<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.expertiza.ncsu.edu/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bdevine2</id>
	<title>Expertiza_Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.expertiza.ncsu.edu/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bdevine2"/>
	<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=Special:Contributions/Bdevine2"/>
	<updated>2026-05-25T11:47:28Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.0</generator>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=156866</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=156866"/>
		<updated>2024-04-24T08:35:20Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Pull Request */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Here is the new implementation of invalid_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  ## This method will return true if the advice and its scores is invalid.&lt;br /&gt;
  # Validates by utilizing the private methods invalid_advice_length? and invalid_advice_scores?&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    invalid_advice_length?(num_advices, question, sorted_advice) ||&lt;br /&gt;
      invalid_advice_scores?(sorted_advice)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  ## Checks to see if the advice is the correct length.&lt;br /&gt;
  #  Checks to see if the number of advices is different than the question_advices or advice is empty&lt;br /&gt;
  def invalid_advice_length?(num_advices, question, sorted_advice)&lt;br /&gt;
    question.question_advices.length != num_advices ||&lt;br /&gt;
      sorted_advice.empty?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  ## Checks to see if the scores are valid&lt;br /&gt;
  # Checks to see if the first and last index of the sorted_advice array are different than expected.&lt;br /&gt;
  def invalid_advice_scores?(sorted_advice)&lt;br /&gt;
    sorted_advice[0].score != @questionnaire.max_question_score ||&lt;br /&gt;
      sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the two new methods invalid_advice_length? and invalid_advice_scores? are in the private field as no other methods need to access them. Additionally, this will not affect any testing which makes refactoring the tests later down the line easier.&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' - Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/8b5db0cd60a23c3b032eda025bec863aa059efaa [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
# Refactor the 'if' statement into a ternary operator while calculating the number of advice. This accomplishes the same logic more concisely.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/a1e26c0679f71df65b18961ba7e26da1deb04fe1 [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
# Modify the advice associated with a questionnaire&lt;br /&gt;
  # Separate methods were introduced to calculate the number of advices and sort the advices related to the current question attribute&lt;br /&gt;
  # This is done to adhere to Single Responsibility Principle&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # For each question in a questionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is scored, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      # # Call to a separate method to adhere to Single Responsibility Principle&lt;br /&gt;
      num_advices = calculate_num_advices(question)&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      # Call to a separate method to adhere to Single Responsibility Principle&lt;br /&gt;
      sorted_advice = sort_question_advices(question)&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Function to calculate number of advices for the current question attribute based on max and min question score.&lt;br /&gt;
  # Method name is consistent with the functionality&lt;br /&gt;
  # Refactored the 'if' statement into a ternary operator. This accomplishes the same logic more concisely.&lt;br /&gt;
  def calculate_num_advices(question)&lt;br /&gt;
    question.is_a?(ScoredQuestion) ? @questionnaire.max_question_score - @questionnaire.min_question_score + 1 : 0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  # Function to sort question advices related to the current question attribute&lt;br /&gt;
  # While sorting questions, sort_by(&amp;amp;:score) is used instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions.&lt;br /&gt;
  def sort_question_advices(question)&lt;br /&gt;
    question.question_advices.sort_by(&amp;amp;:score).reverse&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
While these test are efficient, we wanted to more closely align the tests with the auto generated test skeleton. Additionally, some of the tests are repetitive and would make refactoring more challenging as duplicate tests create an unnecessary headache.&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe &amp;quot;#invalid_advice?&amp;quot; do&lt;br /&gt;
  let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1) }&lt;br /&gt;
  let(:questionAdvice2) { build(:question_advice, id: 2, score: 3, question_id: 1) }&lt;br /&gt;
  let(:questionnaire) do&lt;br /&gt;
    build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
      questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
  end&lt;br /&gt;
    context &amp;quot;when the sorted advice is empty&amp;quot; do&lt;br /&gt;
      it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
        ## Set sorted advice to empty.&lt;br /&gt;
        sorted_advice = []&lt;br /&gt;
        num_advices = 5&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice, num_advices, questionnaire.questions[0])).to be_truthy&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when the number of advices does not match the expected number&amp;quot; do&lt;br /&gt;
      it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
        ## Set sorted advice to be different length the num_advices.&lt;br /&gt;
        sorted_advice = [questionAdvice1, questionAdvice2]&lt;br /&gt;
        num_advices = 1&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice, num_advices, questionnaire.questions[0])).to be_truthy&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when the highest scoring advice does not have the maximum question score&amp;quot; do&lt;br /&gt;
      it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
        # Create a question advice with a score lower than the maximum question score&lt;br /&gt;
        questionAdvice1 = build(:question_advice, id: 1, score: 3, question_id: 1)&lt;br /&gt;
        questionnaire = build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
                              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1])], max_question_score: 5)&lt;br /&gt;
&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by(&amp;amp;:score).reverse&lt;br /&gt;
    &lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice, num_advices, questionnaire.questions[0])).to be_truthy&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when the lowest scoring advice does not have the minimum question score&amp;quot; do&lt;br /&gt;
      it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
        # Create a question advice with a score higher than the minimum question score&lt;br /&gt;
        questionAdvice1 = build(:question_advice, id: 1, score: 1, question_id: 1) # Assuming minimum question score is 2&lt;br /&gt;
        questionnaire = build(:questionnaire, id: 1, min_question_score: 2,&lt;br /&gt;
                              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1])], max_question_score: 5)&lt;br /&gt;
    &lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by(&amp;amp;:score).reverse&lt;br /&gt;
    &lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice, num_advices, questionnaire.questions[0])).to be_truthy&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when invalid_advice? is called with all conditions satisfied' do&lt;br /&gt;
      # Question Advices passing all conditions&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
                              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'invalid_advice? returns false when called with all correct pre-conditions ' do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1&lt;br /&gt;
        controller.instance_variable_set(:@questionnaire, questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice, num_advices, questionnaire.questions[0])).to eq(false)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    &lt;br /&gt;
    &lt;br /&gt;
    &lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton and this is the [https://github.com/AdrYne01/reimplementation-back-end/commit/3be59d5a6edd020e00bf5bba3c97fffae4538aa0 [Commit]] with the changes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
    let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
    let(:questionnaire) do&lt;br /&gt;
      build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
            questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the advice is present' do&lt;br /&gt;
      it 'updates the advice for each key' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        advice_params = {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }&lt;br /&gt;
        params = { advice: advice_params, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # check each key to see if it received update&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        advice_params.keys.each do |advice_key|&lt;br /&gt;
          expect(QuestionAdvice).to have_received(:update).with(advice_key, advice: advice_params[advice_key][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'sets a success flash notice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        params = { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the advice is not present' do&lt;br /&gt;
      it 'does not update any advice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
        # no advice parameter&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # Expect no update to be called with nil params for advice&lt;br /&gt;
        # Expect no flash&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        expect(QuestionAdvice).not_to have_received(:update)&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the questionnaire is not found' do&lt;br /&gt;
      it 'renders the edit_advice view' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_raise(ActiveRecord::RecordNotFound)&lt;br /&gt;
&lt;br /&gt;
        #Act&lt;br /&gt;
        # call get on edit_advice with params that will hit the record not found path&lt;br /&gt;
        get :edit_advice, params: { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
&lt;br /&gt;
        # Assert: Verify that the controller renders the correct view&lt;br /&gt;
        expect(response).to render_template('edit_advice')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    it 'redirects to the edit_advice action' do&lt;br /&gt;
      # Arrange&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
      params = { advice: {&lt;br /&gt;
        '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
        '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
      }, id: 1 }&lt;br /&gt;
      session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
      # Act&lt;br /&gt;
      result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
      # Assert&lt;br /&gt;
      # expect 302 redirect and for it to redirect to edit_advice&lt;br /&gt;
      expect(result.status).to eq 302&lt;br /&gt;
      expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context 'when edit_advice is called and invalid_advice? evaluates to true' do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
                              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'edit advice redirects correctly when called' do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
        result = get(:edit_advice, params:, session:)&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when advice adjustment is not necessary' do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'does not adjust advice size when called' do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(false)&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when the advice size needs adjustment&amp;quot; do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      before do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(true)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;calculates the number of advices for each question&amp;quot; do&lt;br /&gt;
        expect(controller).to receive(:calculate_num_advices).once # Assuming there are two questions in the questionnaire&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;sorts question advices in descending order by score&amp;quot; do&lt;br /&gt;
        expect(controller).to receive(:sort_question_advices).once # Assuming there are two questions in the questionnaire&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the number of advices is less than the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(1) # Assuming only one advice calculated&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the number of advices is greater than the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(3) # Assuming three advices calculated&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the max score of the advices does not correspond to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:sort_question_advices).and_return([questionAdvice2, questionAdvice1]) # Assuming advices not sorted correctly&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the min score of the advices does not correspond to the min score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(questionnaire).to receive(:min_question_score).and_return(0) # Assuming min score not matching&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when the advice size does not need adjustment&amp;quot; do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      before do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(false)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the number of advices is equal to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(2) # Assuming two advices calculated&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the max score of the advices corresponds to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the min score of the advices corresponds to the min score of the questionnaire&amp;quot; do&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from '''ActionController::Base''' to allow for our testing to complete.&lt;br /&gt;
&lt;br /&gt;
'''Recommendation''' - Future work that includes api based controllers should have an APIController they inherit from that is '''APIController &amp;lt; ActionController::API'''. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b [Commit]]&lt;br /&gt;
&lt;br /&gt;
=== SimpleCov Coverage Report ===&lt;br /&gt;
----&lt;br /&gt;
The SimpleCov report shows 93.55% relevant LoC for our advice_controller.rb model file. &lt;br /&gt;
----&lt;br /&gt;
*'''https://adryne01.github.io/Prog4-SimpleCov/Code%20coverage%20for%20Reimplementation-back-end.html'''&lt;br /&gt;
[[File:SimpleCovReport.png]]&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
https://github.com/expertiza/reimplementation-back-end/pull/106&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video (methods and testing) ==&lt;br /&gt;
https://www.youtube.com/watch?v=AhPnEzIC5s4&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155756</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155756"/>
		<updated>2024-04-21T21:22:26Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Video */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' - Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/8b5db0cd60a23c3b032eda025bec863aa059efaa [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
# Refactor the 'if' statement into a ternary operator while calculating the number of advice. This accomplishes the same logic more concisely.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/a1e26c0679f71df65b18961ba7e26da1deb04fe1 [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
# Modify the advice associated with a questionnaire&lt;br /&gt;
  # Separate methods were introduced to calculate the number of advices and sort the advices related to the current question attribute&lt;br /&gt;
  # This is done to adhere to Single Responsibility Principle&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # For each question in a questionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is scored, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      # # Call to a separate method to adhere to Single Responsibility Principle&lt;br /&gt;
      num_advices = calculate_num_advices(question)&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      # Call to a separate method to adhere to Single Responsibility Principle&lt;br /&gt;
      sorted_advice = sort_question_advices(question)&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Function to calculate number of advices for the current question attribute based on max and min question score.&lt;br /&gt;
  # Method name is consistent with the functionality&lt;br /&gt;
  # Refactored the 'if' statement into a ternary operator. This accomplishes the same logic more concisely.&lt;br /&gt;
  def calculate_num_advices(question)&lt;br /&gt;
    question.is_a?(ScoredQuestion) ? @questionnaire.max_question_score - @questionnaire.min_question_score + 1 : 0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  # Function to sort question advices related to the current question attribute&lt;br /&gt;
  # While sorting questions, sort_by(&amp;amp;:score) is used instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions.&lt;br /&gt;
  def sort_question_advices(question)&lt;br /&gt;
    question.question_advices.sort_by(&amp;amp;:score).reverse&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton and this is the [https://github.com/AdrYne01/reimplementation-back-end/commit/3be59d5a6edd020e00bf5bba3c97fffae4538aa0 [Commit]] with the changes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
    let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
    let(:questionnaire) do&lt;br /&gt;
      build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
            questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the advice is present' do&lt;br /&gt;
      it 'updates the advice for each key' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        advice_params = {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }&lt;br /&gt;
        params = { advice: advice_params, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # check each key to see if it received update&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        advice_params.keys.each do |advice_key|&lt;br /&gt;
          expect(QuestionAdvice).to have_received(:update).with(advice_key, advice: advice_params[advice_key][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'sets a success flash notice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        params = { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the advice is not present' do&lt;br /&gt;
      it 'does not update any advice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
        # no advice parameter&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # Expect no update to be called with nil params for advice&lt;br /&gt;
        # Expect no flash&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        expect(QuestionAdvice).not_to have_received(:update)&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the questionnaire is not found' do&lt;br /&gt;
      it 'renders the edit_advice view' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_raise(ActiveRecord::RecordNotFound)&lt;br /&gt;
&lt;br /&gt;
        #Act&lt;br /&gt;
        # call get on edit_advice with params that will hit the record not found path&lt;br /&gt;
        get :edit_advice, params: { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
&lt;br /&gt;
        # Assert: Verify that the controller renders the correct view&lt;br /&gt;
        expect(response).to render_template('edit_advice')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    it 'redirects to the edit_advice action' do&lt;br /&gt;
      # Arrange&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
      params = { advice: {&lt;br /&gt;
        '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
        '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
      }, id: 1 }&lt;br /&gt;
      session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
      # Act&lt;br /&gt;
      result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
      # Assert&lt;br /&gt;
      # expect 302 redirect and for it to redirect to edit_advice&lt;br /&gt;
      expect(result.status).to eq 302&lt;br /&gt;
      expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context 'when edit_advice is called and invalid_advice? evaluates to true' do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
                              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'edit advice redirects correctly when called' do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
        result = get(:edit_advice, params:, session:)&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when advice adjustment is not necessary' do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'does not adjust advice size when called' do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(false)&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when the advice size needs adjustment&amp;quot; do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      before do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(true)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;calculates the number of advices for each question&amp;quot; do&lt;br /&gt;
        expect(controller).to receive(:calculate_num_advices).once # Assuming there are two questions in the questionnaire&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;sorts question advices in descending order by score&amp;quot; do&lt;br /&gt;
        expect(controller).to receive(:sort_question_advices).once # Assuming there are two questions in the questionnaire&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the number of advices is less than the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(1) # Assuming only one advice calculated&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the number of advices is greater than the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(3) # Assuming three advices calculated&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the max score of the advices does not correspond to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:sort_question_advices).and_return([questionAdvice2, questionAdvice1]) # Assuming advices not sorted correctly&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the min score of the advices does not correspond to the min score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(questionnaire).to receive(:min_question_score).and_return(0) # Assuming min score not matching&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when the advice size does not need adjustment&amp;quot; do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      before do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(false)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the number of advices is equal to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(2) # Assuming two advices calculated&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the max score of the advices corresponds to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the min score of the advices corresponds to the min score of the questionnaire&amp;quot; do&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from '''ActionController::Base''' to allow for our testing to complete.&lt;br /&gt;
&lt;br /&gt;
'''Recommendation''' - Future work that includes api based controllers should have an APIController they inherit from that is '''APIController &amp;lt; ActionController::API'''. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b [Commit]]&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
https://github.com/expertiza/reimplementation-back-end/pull/99&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video (methods and testing) ==&lt;br /&gt;
https://www.youtube.com/watch?v=AhPnEzIC5s4&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155755</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155755"/>
		<updated>2024-04-21T21:21:55Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Video */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' - Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/8b5db0cd60a23c3b032eda025bec863aa059efaa [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
# Refactor the 'if' statement into a ternary operator while calculating the number of advice. This accomplishes the same logic more concisely.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/a1e26c0679f71df65b18961ba7e26da1deb04fe1 [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
# Modify the advice associated with a questionnaire&lt;br /&gt;
  # Separate methods were introduced to calculate the number of advices and sort the advices related to the current question attribute&lt;br /&gt;
  # This is done to adhere to Single Responsibility Principle&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # For each question in a questionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is scored, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      # # Call to a separate method to adhere to Single Responsibility Principle&lt;br /&gt;
      num_advices = calculate_num_advices(question)&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      # Call to a separate method to adhere to Single Responsibility Principle&lt;br /&gt;
      sorted_advice = sort_question_advices(question)&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Function to calculate number of advices for the current question attribute based on max and min question score.&lt;br /&gt;
  # Method name is consistent with the functionality&lt;br /&gt;
  # Refactored the 'if' statement into a ternary operator. This accomplishes the same logic more concisely.&lt;br /&gt;
  def calculate_num_advices(question)&lt;br /&gt;
    question.is_a?(ScoredQuestion) ? @questionnaire.max_question_score - @questionnaire.min_question_score + 1 : 0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  # Function to sort question advices related to the current question attribute&lt;br /&gt;
  # While sorting questions, sort_by(&amp;amp;:score) is used instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions.&lt;br /&gt;
  def sort_question_advices(question)&lt;br /&gt;
    question.question_advices.sort_by(&amp;amp;:score).reverse&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton and this is the [https://github.com/AdrYne01/reimplementation-back-end/commit/3be59d5a6edd020e00bf5bba3c97fffae4538aa0 [Commit]] with the changes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
    let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
    let(:questionnaire) do&lt;br /&gt;
      build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
            questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the advice is present' do&lt;br /&gt;
      it 'updates the advice for each key' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        advice_params = {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }&lt;br /&gt;
        params = { advice: advice_params, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # check each key to see if it received update&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        advice_params.keys.each do |advice_key|&lt;br /&gt;
          expect(QuestionAdvice).to have_received(:update).with(advice_key, advice: advice_params[advice_key][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'sets a success flash notice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        params = { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the advice is not present' do&lt;br /&gt;
      it 'does not update any advice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
        # no advice parameter&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # Expect no update to be called with nil params for advice&lt;br /&gt;
        # Expect no flash&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        expect(QuestionAdvice).not_to have_received(:update)&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the questionnaire is not found' do&lt;br /&gt;
      it 'renders the edit_advice view' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_raise(ActiveRecord::RecordNotFound)&lt;br /&gt;
&lt;br /&gt;
        #Act&lt;br /&gt;
        # call get on edit_advice with params that will hit the record not found path&lt;br /&gt;
        get :edit_advice, params: { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
&lt;br /&gt;
        # Assert: Verify that the controller renders the correct view&lt;br /&gt;
        expect(response).to render_template('edit_advice')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    it 'redirects to the edit_advice action' do&lt;br /&gt;
      # Arrange&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
      params = { advice: {&lt;br /&gt;
        '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
        '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
      }, id: 1 }&lt;br /&gt;
      session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
      # Act&lt;br /&gt;
      result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
      # Assert&lt;br /&gt;
      # expect 302 redirect and for it to redirect to edit_advice&lt;br /&gt;
      expect(result.status).to eq 302&lt;br /&gt;
      expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context 'when edit_advice is called and invalid_advice? evaluates to true' do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
                              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'edit advice redirects correctly when called' do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
        result = get(:edit_advice, params:, session:)&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when advice adjustment is not necessary' do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'does not adjust advice size when called' do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(false)&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when the advice size needs adjustment&amp;quot; do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      before do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(true)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;calculates the number of advices for each question&amp;quot; do&lt;br /&gt;
        expect(controller).to receive(:calculate_num_advices).once # Assuming there are two questions in the questionnaire&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;sorts question advices in descending order by score&amp;quot; do&lt;br /&gt;
        expect(controller).to receive(:sort_question_advices).once # Assuming there are two questions in the questionnaire&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the number of advices is less than the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(1) # Assuming only one advice calculated&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the number of advices is greater than the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(3) # Assuming three advices calculated&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the max score of the advices does not correspond to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:sort_question_advices).and_return([questionAdvice2, questionAdvice1]) # Assuming advices not sorted correctly&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the min score of the advices does not correspond to the min score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(questionnaire).to receive(:min_question_score).and_return(0) # Assuming min score not matching&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when the advice size does not need adjustment&amp;quot; do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      before do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(false)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the number of advices is equal to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(2) # Assuming two advices calculated&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the max score of the advices corresponds to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the min score of the advices corresponds to the min score of the questionnaire&amp;quot; do&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from '''ActionController::Base''' to allow for our testing to complete.&lt;br /&gt;
&lt;br /&gt;
'''Recommendation''' - Future work that includes api based controllers should have an APIController they inherit from that is '''APIController &amp;lt; ActionController::API'''. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b [Commit]]&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
https://github.com/expertiza/reimplementation-back-end/pull/99&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;br /&gt;
https://www.youtube.com/watch?v=AhPnEzIC5s4&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155728</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155728"/>
		<updated>2024-04-21T19:06:43Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Pull Request */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' - Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/8b5db0cd60a23c3b032eda025bec863aa059efaa [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
# Refactor the 'if' statement into a ternary operator while calculating the number of advice. This accomplishes the same logic more concisely.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/a1e26c0679f71df65b18961ba7e26da1deb04fe1 [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
# Modify the advice associated with a questionnaire&lt;br /&gt;
  # Separate methods were introduced to calculate the number of advices and sort the advices related to the current question attribute&lt;br /&gt;
  # This is done to adhere to Single Responsibility Principle&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # For each question in a questionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is scored, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      # # Call to a separate method to adhere to Single Responsibility Principle&lt;br /&gt;
      num_advices = calculate_num_advices(question)&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      # Call to a separate method to adhere to Single Responsibility Principle&lt;br /&gt;
      sorted_advice = sort_question_advices(question)&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Function to calculate number of advices for the current question attribute based on max and min question score.&lt;br /&gt;
  # Method name is consistent with the functionality&lt;br /&gt;
  # Refactored the 'if' statement into a ternary operator. This accomplishes the same logic more concisely.&lt;br /&gt;
  def calculate_num_advices(question)&lt;br /&gt;
    question.is_a?(ScoredQuestion) ? @questionnaire.max_question_score - @questionnaire.min_question_score + 1 : 0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  # Function to sort question advices related to the current question attribute&lt;br /&gt;
  # While sorting questions, sort_by(&amp;amp;:score) is used instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions.&lt;br /&gt;
  def sort_question_advices(question)&lt;br /&gt;
    question.question_advices.sort_by(&amp;amp;:score).reverse&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton and this is the [https://github.com/AdrYne01/reimplementation-back-end/commit/3be59d5a6edd020e00bf5bba3c97fffae4538aa0 [Commit]] with the changes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
    let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
    let(:questionnaire) do&lt;br /&gt;
      build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
            questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the advice is present' do&lt;br /&gt;
      it 'updates the advice for each key' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        advice_params = {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }&lt;br /&gt;
        params = { advice: advice_params, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # check each key to see if it received update&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        advice_params.keys.each do |advice_key|&lt;br /&gt;
          expect(QuestionAdvice).to have_received(:update).with(advice_key, advice: advice_params[advice_key][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'sets a success flash notice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        params = { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the advice is not present' do&lt;br /&gt;
      it 'does not update any advice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
        # no advice parameter&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # Expect no update to be called with nil params for advice&lt;br /&gt;
        # Expect no flash&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        expect(QuestionAdvice).not_to have_received(:update)&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the questionnaire is not found' do&lt;br /&gt;
      it 'renders the edit_advice view' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_raise(ActiveRecord::RecordNotFound)&lt;br /&gt;
&lt;br /&gt;
        #Act&lt;br /&gt;
        # call get on edit_advice with params that will hit the record not found path&lt;br /&gt;
        get :edit_advice, params: { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
&lt;br /&gt;
        # Assert: Verify that the controller renders the correct view&lt;br /&gt;
        expect(response).to render_template('edit_advice')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    it 'redirects to the edit_advice action' do&lt;br /&gt;
      # Arrange&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
      params = { advice: {&lt;br /&gt;
        '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
        '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
      }, id: 1 }&lt;br /&gt;
      session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
      # Act&lt;br /&gt;
      result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
      # Assert&lt;br /&gt;
      # expect 302 redirect and for it to redirect to edit_advice&lt;br /&gt;
      expect(result.status).to eq 302&lt;br /&gt;
      expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context 'when edit_advice is called and invalid_advice? evaluates to true' do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
                              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'edit advice redirects correctly when called' do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
        result = get(:edit_advice, params:, session:)&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when advice adjustment is not necessary' do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'does not adjust advice size when called' do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(false)&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when the advice size needs adjustment&amp;quot; do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      before do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(true)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;calculates the number of advices for each question&amp;quot; do&lt;br /&gt;
        expect(controller).to receive(:calculate_num_advices).once # Assuming there are two questions in the questionnaire&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;sorts question advices in descending order by score&amp;quot; do&lt;br /&gt;
        expect(controller).to receive(:sort_question_advices).once # Assuming there are two questions in the questionnaire&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the number of advices is less than the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(1) # Assuming only one advice calculated&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the number of advices is greater than the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(3) # Assuming three advices calculated&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the max score of the advices does not correspond to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:sort_question_advices).and_return([questionAdvice2, questionAdvice1]) # Assuming advices not sorted correctly&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;adjusts the advice size if the min score of the advices does not correspond to the min score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(questionnaire).to receive(:min_question_score).and_return(0) # Assuming min score not matching&lt;br /&gt;
        expect(QuestionnaireHelper).to receive(:adjust_advice_size).with(questionnaire, questionnaire.questions.first)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when the advice size does not need adjustment&amp;quot; do&lt;br /&gt;
      let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
      let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
              questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      before do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(controller).to receive(:invalid_advice?).and_return(false)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the number of advices is equal to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        allow(controller).to receive(:calculate_num_advices).and_return(2) # Assuming two advices calculated&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the max score of the advices corresponds to the max score of the questionnaire&amp;quot; do&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not adjust the advice size if the min score of the advices corresponds to the min score of the questionnaire&amp;quot; do&lt;br /&gt;
        expect(QuestionnaireHelper).not_to receive(:adjust_advice_size)&lt;br /&gt;
        get :edit_advice, params: { id: 1 }&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from '''ActionController::Base''' to allow for our testing to complete.&lt;br /&gt;
&lt;br /&gt;
'''Recommendation''' - Future work that includes api based controllers should have an APIController they inherit from that is '''APIController &amp;lt; ActionController::API'''. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b [Commit]]&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
https://github.com/expertiza/reimplementation-back-end/pull/99&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155694</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155694"/>
		<updated>2024-04-17T14:32:55Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' - Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/8b5db0cd60a23c3b032eda025bec863aa059efaa [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 [Commit]]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton and this is the [https://github.com/AdrYne01/reimplementation-back-end/commit/3be59d5a6edd020e00bf5bba3c97fffae4538aa0 [Commit]] with the changes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
    let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
    let(:questionnaire) do&lt;br /&gt;
      build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
            questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the advice is present' do&lt;br /&gt;
      it 'updates the advice for each key' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        advice_params = {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }&lt;br /&gt;
        params = { advice: advice_params, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # check each key to see if it received update&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        advice_params.keys.each do |advice_key|&lt;br /&gt;
          expect(QuestionAdvice).to have_received(:update).with(advice_key, advice: advice_params[advice_key][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'sets a success flash notice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        params = { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the advice is not present' do&lt;br /&gt;
      it 'does not update any advice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
        # no advice parameter&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # Expect no update to be called with nil params for advice&lt;br /&gt;
        # Expect no flash&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        expect(QuestionAdvice).not_to have_received(:update)&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the questionnaire is not found' do&lt;br /&gt;
      it 'renders the edit_advice view' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_raise(ActiveRecord::RecordNotFound)&lt;br /&gt;
&lt;br /&gt;
        #Act&lt;br /&gt;
        # call get on edit_advice with params that will hit the record not found path&lt;br /&gt;
        get :edit_advice, params: { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
&lt;br /&gt;
        # Assert: Verify that the controller renders the correct view&lt;br /&gt;
        expect(response).to render_template('edit_advice')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    it 'redirects to the edit_advice action' do&lt;br /&gt;
      # Arrange&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
      params = { advice: {&lt;br /&gt;
        '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
        '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
      }, id: 1 }&lt;br /&gt;
      session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
      # Act&lt;br /&gt;
      result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
      # Assert&lt;br /&gt;
      # expect 302 redirect and for it to redirect to edit_advice&lt;br /&gt;
      expect(result.status).to eq 302&lt;br /&gt;
      expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from '''ActionController::Base''' to allow for our testing to complete.&lt;br /&gt;
&lt;br /&gt;
'''Recommendation''' - Future work that includes api based controllers should have an APIController they inherit from that is '''APIController &amp;lt; ActionController::API'''. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b [Commit]]&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155693</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155693"/>
		<updated>2024-04-17T00:28:13Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* save_advice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code''' - Refactored code [https://github.com/AdrYne01/reimplementation-back-end/commit/8b5db0cd60a23c3b032eda025bec863aa059efaa Commit]&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 Commit]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton and this is the [https://github.com/AdrYne01/reimplementation-back-end/commit/3be59d5a6edd020e00bf5bba3c97fffae4538aa0 Commit] with the changes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
    let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
    let(:questionnaire) do&lt;br /&gt;
      build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
            questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the advice is present' do&lt;br /&gt;
      it 'updates the advice for each key' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        advice_params = {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }&lt;br /&gt;
        params = { advice: advice_params, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # check each key to see if it received update&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        advice_params.keys.each do |advice_key|&lt;br /&gt;
          expect(QuestionAdvice).to have_received(:update).with(advice_key, advice: advice_params[advice_key][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'sets a success flash notice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        params = { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the advice is not present' do&lt;br /&gt;
      it 'does not update any advice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
        # no advice parameter&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # Expect no update to be called with nil params for advice&lt;br /&gt;
        # Expect no flash&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        expect(QuestionAdvice).not_to have_received(:update)&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the questionnaire is not found' do&lt;br /&gt;
      it 'renders the edit_advice view' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_raise(ActiveRecord::RecordNotFound)&lt;br /&gt;
&lt;br /&gt;
        #Act&lt;br /&gt;
        # call get on edit_advice with params that will hit the record not found path&lt;br /&gt;
        get :edit_advice, params: { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
&lt;br /&gt;
        # Assert: Verify that the controller renders the correct view&lt;br /&gt;
        expect(response).to render_template('edit_advice')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    it 'redirects to the edit_advice action' do&lt;br /&gt;
      # Arrange&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
      params = { advice: {&lt;br /&gt;
        '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
        '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
      }, id: 1 }&lt;br /&gt;
      session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
      # Act&lt;br /&gt;
      result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
      # Assert&lt;br /&gt;
      # expect 302 redirect and for it to redirect to edit_advice&lt;br /&gt;
      expect(result.status).to eq 302&lt;br /&gt;
      expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from '''ActionController::Base''' to allow for our testing to complete.&lt;br /&gt;
&lt;br /&gt;
'''Recommendation''' - Future work that includes api based controllers should have an APIController they inherit from that is '''APIController &amp;lt; ActionController::API'''. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b Commit]&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155692</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155692"/>
		<updated>2024-04-17T00:26:44Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* save_advice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 Commit]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the tests after refactoring with the test skeleton and this is the [https://github.com/AdrYne01/reimplementation-back-end/commit/3be59d5a6edd020e00bf5bba3c97fffae4538aa0 Commit] with the changes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    let(:questionAdvice1) { build(:question_advice, id: 1, score: 1, question_id: 1, advice: 'Advice1') }&lt;br /&gt;
    let(:questionAdvice2) { build(:question_advice, id: 2, score: 2, question_id: 1, advice: 'Advice2') }&lt;br /&gt;
    let(:questionnaire) do&lt;br /&gt;
      build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
            questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1, questionAdvice2])], max_question_score: 2)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the advice is present' do&lt;br /&gt;
      it 'updates the advice for each key' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        advice_params = {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }&lt;br /&gt;
        params = { advice: advice_params, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # check each key to see if it received update&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        advice_params.keys.each do |advice_key|&lt;br /&gt;
          expect(QuestionAdvice).to have_received(:update).with(advice_key, advice: advice_params[advice_key][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it 'sets a success flash notice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1', { advice: 'Hello' }).and_return('Ok')&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('2', { advice: 'Goodbye' }).and_return('Ok')&lt;br /&gt;
        # Add some advice parameters that will allow for update success&lt;br /&gt;
        params = { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the advice is not present' do&lt;br /&gt;
      it 'does not update any advice' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
        # no advice parameter&lt;br /&gt;
        params = { id: 1 }&lt;br /&gt;
        session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
        # Act&lt;br /&gt;
        result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
        # Assert&lt;br /&gt;
        # Expect no update to be called with nil params for advice&lt;br /&gt;
        # Expect no flash&lt;br /&gt;
        # Always expect redirect&lt;br /&gt;
        expect(QuestionAdvice).not_to have_received(:update)&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context 'when the questionnaire is not found' do&lt;br /&gt;
      it 'renders the edit_advice view' do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_raise(ActiveRecord::RecordNotFound)&lt;br /&gt;
&lt;br /&gt;
        #Act&lt;br /&gt;
        # call get on edit_advice with params that will hit the record not found path&lt;br /&gt;
        get :edit_advice, params: { advice: {&lt;br /&gt;
          '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
          '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
        }, id: 1 }&lt;br /&gt;
&lt;br /&gt;
        # Assert: Verify that the controller renders the correct view&lt;br /&gt;
        expect(response).to render_template('edit_advice')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    it 'redirects to the edit_advice action' do&lt;br /&gt;
      # Arrange&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(QuestionAdvice).to receive(:update).with(any_args).and_return('Ok')&lt;br /&gt;
      params = { advice: {&lt;br /&gt;
        '1' =&amp;gt; { advice: 'Hello' },&lt;br /&gt;
        '2' =&amp;gt; { advice: 'Goodbye' }&lt;br /&gt;
      }, id: 1 }&lt;br /&gt;
      session = { user: instructor1 }&lt;br /&gt;
&lt;br /&gt;
      # Act&lt;br /&gt;
      result = get(:save_advice, params:, session:)&lt;br /&gt;
&lt;br /&gt;
      # Assert&lt;br /&gt;
      # expect 302 redirect and for it to redirect to edit_advice&lt;br /&gt;
      expect(result.status).to eq 302&lt;br /&gt;
      expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from '''ActionController::Base''' to allow for our testing to complete.&lt;br /&gt;
&lt;br /&gt;
'''Recommendation''' - Future work that includes api based controllers should have an APIController they inherit from that is '''APIController &amp;lt; ActionController::API'''. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b Commit]&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155691</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155691"/>
		<updated>2024-04-17T00:24:36Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Testing Note 1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 Commit]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from '''ActionController::Base''' to allow for our testing to complete.&lt;br /&gt;
&lt;br /&gt;
'''Recommendation''' - Future work that includes api based controllers should have an APIController they inherit from that is '''APIController &amp;lt; ActionController::API'''. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b Commit]&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155690</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155690"/>
		<updated>2024-04-17T00:24:25Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Testing Note 1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 Commit]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from '''ActionController::Base''' to allow for our testing to complete.&lt;br /&gt;
&lt;br /&gt;
'''Recommendation''' Future work that includes api based controllers should have an APIController they inherit from that is '''APIController &amp;lt; ActionController::API'''. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b Commit]&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155689</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155689"/>
		<updated>2024-04-17T00:24:01Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Testing (rspec) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 Commit]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Testing Note 1 ======&lt;br /&gt;
During our initial run of tests with no altered code we were getting a few failure items.  The two major ones were related to a missing edit erb file and an issue with the tests finding the method flash.  &lt;br /&gt;
Upon researching I noticed that the ApplicationController inherited from '''ActionController::API''' which does not contain the method for flash. The refactor fork contained quite a few api based controllers which this makes sense for. This does not make sense for non API related controllers like AdviceController. In my PR I have modified ApplicationController to inherit from ActionController::Base to allow for our testing to complete.&lt;br /&gt;
'''Recommendation''' Future work that includes api based controllers should have an APIController they inherit from that is APIController &amp;lt; ActionController::API. The changes made based on this discovery are here in this [https://github.com/AdrYne01/reimplementation-back-end/commit/bebf4a15bbb9d8b5d1d0c6f1ac77c664c261be1b Commit]&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155688</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155688"/>
		<updated>2024-04-17T00:18:06Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Methods */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 Commit]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155687</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155687"/>
		<updated>2024-04-17T00:17:48Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Note 1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155686</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155686"/>
		<updated>2024-04-17T00:17:16Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Note 1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces and the code [https://github.com/AdrYne01/reimplementation-back-end/commit/e882f7b49c9edad41705d5c6b7582936069fa279 Commit]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155685</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155685"/>
		<updated>2024-04-17T00:13:22Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* save_advice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Note 1 ======&lt;br /&gt;
After completing the initial refactoring there was an issue with one item that was clearly not '''DRY'''. The line of code @questionnaire = Questionnaire.find(params[:id]) which was in both the save and edit methods.&lt;br /&gt;
&lt;br /&gt;
A private method set_questionnaire was created and assigned in a before_action at the top of the controller.  The following is the refactored pieces&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  before_action :set_questionnaire, only: %i[ edit_advice save_advice ]&lt;br /&gt;
&lt;br /&gt;
  def set_questionnaire&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155684</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=155684"/>
		<updated>2024-04-17T00:09:31Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* save_advice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
  def save_advice&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].each do |advice_key, advice_params|&lt;br /&gt;
          # get existing advice to update by key with the passed in advice param&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: advice_params.slice(:advice)[:advice])&lt;br /&gt;
        end&lt;br /&gt;
        # we made it here so it was saved&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice and sends flash&lt;br /&gt;
      flash[:notice] = 'The advice record was not found and saved!'&lt;br /&gt;
    end&lt;br /&gt;
    # regardless of action above redirect to edit and show flash message if one exists&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores. We can also add tests to check the actions of the controller when the invalid_advice? method returns false. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2422_Refactor_Advice_Controller&amp;diff=154904</id>
		<title>CSC/ECE 517 Spring 2024 - E2422 Refactor Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2422_Refactor_Advice_Controller&amp;diff=154904"/>
		<updated>2024-04-07T21:07:46Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: Bdevine2 moved page CSC/ECE 517 Spring 2024 - E2422 Refactor Advice Controller to CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller: Misspelled title&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller]]&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154903</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154903"/>
		<updated>2024-04-07T21:07:46Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: Bdevine2 moved page CSC/ECE 517 Spring 2024 - E2422 Refactor Advice Controller to CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller: Misspelled title&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154871</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154871"/>
		<updated>2024-04-07T18:50:17Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Project Description */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* '''action_allowed?'''&lt;br /&gt;
* '''invalid_advice?'''&lt;br /&gt;
* '''edit_advice'''&lt;br /&gt;
* '''save_advice'''&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154870</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154870"/>
		<updated>2024-04-07T18:44:35Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Problem Document */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
https://docs.google.com/document/d/1nmJ9ce_Or6hRucsVpq4m_IFN4aKH6N1NepEpMGfGqbM/edit#heading=h.531sshwnqkr&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154869</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154869"/>
		<updated>2024-04-07T18:42:33Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;br /&gt;
&lt;br /&gt;
== Pull Request ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problem Document ==&lt;br /&gt;
&lt;br /&gt;
== Video ==&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154868</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154868"/>
		<updated>2024-04-07T18:36:03Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice?''' ====&lt;br /&gt;
Here is the current implementation of invalid_advice?&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # checks whether the advices for a question in questionnaire have valid attributes&lt;br /&gt;
  # return true if the number of advices and their scores are invalid, else returns false&lt;br /&gt;
  def invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
    return ((question.question_advices.length != num_advices) ||&lt;br /&gt;
    sorted_advice.empty? ||&lt;br /&gt;
    (sorted_advice[0].score != @questionnaire.max_question_score) ||&lt;br /&gt;
    (sorted_advice[sorted_advice.length - 1].score != @questionnaire.min_question_score))&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to Refactor'''&lt;br /&gt;
# First, the comments are a bit hard to read, but those can easily be switched.&lt;br /&gt;
# Rather than checking if advice is not invalid, (question.question_advices.length != num_advices) we want to implement this method to be valid_advice where it returns if the length is valid.&lt;br /&gt;
# We want to split the checks up into different methods as per the Single Responsibliity Principle.&lt;br /&gt;
# We do not believe there needs to be any logic changes and all the checks are valuable, and we could not come up with any other &lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here is the current implementation of edit_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  def edit_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
&lt;br /&gt;
    # For each question in a quentionnaire, this method adjusts the advice size if the advice size is &amp;lt;,&amp;gt; number of advices or&lt;br /&gt;
    # the max or min score of the advices does not correspond to the max or min score of questionnaire respectively.&lt;br /&gt;
    @questionnaire.questions.each do |question|&lt;br /&gt;
      # if the question is a scored question, store the number of advices corresponding to that question (max_score - min_score), else 0&lt;br /&gt;
      num_advices = if question.is_a?(ScoredQuestion)&lt;br /&gt;
                      @questionnaire.max_question_score - @questionnaire.min_question_score + 1&lt;br /&gt;
                    else&lt;br /&gt;
                      0&lt;br /&gt;
                    end&lt;br /&gt;
&lt;br /&gt;
      # sorting question advices in descending order by score&lt;br /&gt;
      sorted_advice = question.question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
&lt;br /&gt;
      # Checks the condition for adjusting the advice size&lt;br /&gt;
      if invalid_advice?(sorted_advice, num_advices, question)&lt;br /&gt;
        # The number of advices for this question has changed.&lt;br /&gt;
        QuestionnaireHelper.adjust_advice_size(@questionnaire, question)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# We can have separate functions to do all the different operations in the edit_advice method. &lt;br /&gt;
# Have separate functions for calculating num_advices and sorting the questions by score&lt;br /&gt;
# This is done to make sure the edit_advice method follows the Single Responsibility Principle. &lt;br /&gt;
# While sorting questions, we can use sort_by(&amp;amp;:score) instead of using a block. It is a shorthand notation and avoids creating a new Proc object for every element in the collection of the questions. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''invalid_advice''' ====&lt;br /&gt;
Here are the current tests for invalid_advice? method. As mentioned before, if this method is changed to valid_advice?, the tests will be changed and flipped accordingly. There are some edge cases that we will add like what question advice score = max score of questionnaire or advice score = min score of questionnaire.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   context &amp;quot;when invalid_advice? is called with question advice score &amp;gt; max score of questionnaire&amp;quot; do&lt;br /&gt;
      # max score of advice = 3 (!=2)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 3, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect maximum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1          &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with question advice score &amp;lt; min score of questionnaire&amp;quot; do&lt;br /&gt;
      # min score of advice = 0 (!=1)&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 0, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect minimum score for a question advice&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when invalid_advice? is called with number of advices &amp;gt; (max-min) score of questionnaire&amp;quot; do&lt;br /&gt;
      # number of advices &amp;gt; 2&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice3) {build(:question_advice, id:3, score: 2, question_id: 1, advice: &amp;quot;Advice3&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2,questionAdvice3])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;invalid_advice? returns true when called with incorrect number of question advices&amp;quot; do&lt;br /&gt;
        sorted_advice = questionnaire.questions[0].question_advices.sort_by { |x| x.score }.reverse&lt;br /&gt;
        num_advices = questionnaire.max_question_score - questionnaire.min_question_score + 1         &lt;br /&gt;
        controller.instance_variable_set(:@questionnaire,questionnaire)&lt;br /&gt;
        expect(controller.invalid_advice?(sorted_advice,num_advices,questionnaire.questions[0])).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which we will test before and after the refactoring. We will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== '''edit_advice''' ====&lt;br /&gt;
Here are the current tests for edit_advice. The current tests cover most of the functionalities of the edit_advice method. Tests can be added to evaluate the impact of invalid_advice? method on the edit_advice method and checking if the number of advice is correctly calculated based on the questionnaire's min and max scores.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#edit_advice' do&lt;br /&gt;
&lt;br /&gt;
    context &amp;quot;when edit_advice is called and invalid_advice? evaluates to true&amp;quot; do&lt;br /&gt;
      # edit advice called&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;edit advice redirects correctly when called&amp;quot; do&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :edit_advice, params: params, session: session&lt;br /&gt;
        expect(result.status).to eq 200&lt;br /&gt;
        expect(result).to render_template(:edit_advice)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154770</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154770"/>
		<updated>2024-04-04T08:09:35Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* save_advice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here are the current tests for save_advice. They do have complete coverage for the testing save_advice which I will test before and after the refactoring. I will be modifying the does not save piece to ensure the new flash message on failure shows.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  describe '#save_advice' do&lt;br /&gt;
    context &amp;quot;when save_advice is called&amp;quot; do&lt;br /&gt;
      # When ad advice is saved&lt;br /&gt;
      let(:questionAdvice1) {build(:question_advice, id:1, score: 1, question_id: 1, advice: &amp;quot;Advice1&amp;quot;)}&lt;br /&gt;
      let(:questionAdvice2) {build(:question_advice, id:2, score: 2, question_id: 1, advice: &amp;quot;Advice2&amp;quot;)}&lt;br /&gt;
      let(:questionnaire) do&lt;br /&gt;
        build(:questionnaire, id: 1, min_question_score: 1,&lt;br /&gt;
          questions: [build(:question, id: 1, weight: 2, question_advices: [questionAdvice1,questionAdvice2])], max_question_score: 2)&lt;br /&gt;
      end&lt;br /&gt;
      &lt;br /&gt;
      it &amp;quot;saves advice successfully&amp;quot; do&lt;br /&gt;
        # When an advice is saved successfully&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with('1',{:advice =&amp;gt; &amp;quot;Hello&amp;quot;}).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {advice: {&amp;quot;1&amp;quot; =&amp;gt; {:advice =&amp;gt; &amp;quot;Hello&amp;quot;}}, id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).to eq('The advice was successfully saved!')&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      it &amp;quot;does not save the advice&amp;quot; do&lt;br /&gt;
        # When an advice is not saved&lt;br /&gt;
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
        allow(QuestionAdvice).to receive(:update).with(any_args).and_return(&amp;quot;Ok&amp;quot;)&lt;br /&gt;
        params = {id: 1}&lt;br /&gt;
        session = {user: instructor1}&lt;br /&gt;
        result = get :save_advice, params: params, session: session&lt;br /&gt;
        expect(flash[:notice]).not_to be_present&lt;br /&gt;
        expect(result.status).to eq 302&lt;br /&gt;
        expect(result).to redirect_to('/advice/edit_advice?id=1')&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154769</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154769"/>
		<updated>2024-04-04T08:02:50Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* save_advice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Refactored code'''&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
..&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154768</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154768"/>
		<updated>2024-04-04T08:02:09Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* save_advice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
# Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
# Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154767</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154767"/>
		<updated>2024-04-04T08:01:42Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* save_advice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Plans to refactor'''&lt;br /&gt;
1. Simplify the flow since all paths lead to redirect_to 'edit_advice' we can remove the rescue call and replace that with a failed flash message&lt;br /&gt;
2. Refactor the .each to iterate over params and more clearly handle the update of advice&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154766</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154766"/>
		<updated>2024-04-04T07:55:52Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* save_advice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
Here is the current implementation of save_advice&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  # save the advice for a questionnaire&lt;br /&gt;
  def save_advice&lt;br /&gt;
    # Stores the questionnaire with given id in URL&lt;br /&gt;
    @questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    begin&lt;br /&gt;
      # checks if advice is present or not&lt;br /&gt;
      unless params[:advice].nil?&lt;br /&gt;
        params[:advice].keys.each do |advice_key|&lt;br /&gt;
          # Updates the advice corresponding to the key&lt;br /&gt;
          QuestionAdvice.update(advice_key, advice: params[:advice][advice_key.to_sym][:advice])&lt;br /&gt;
        end&lt;br /&gt;
        flash[:notice] = 'The advice was successfully saved!'&lt;br /&gt;
      end&lt;br /&gt;
    rescue ActiveRecord::RecordNotFound&lt;br /&gt;
      # If record not found, redirects to edit_advice&lt;br /&gt;
      render action: 'edit_advice', id: params[:id]&lt;br /&gt;
    end&lt;br /&gt;
    redirect_to action: 'edit_advice', id: params[:id]&lt;br /&gt;
  end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154765</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154765"/>
		<updated>2024-04-04T07:50:22Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Methods''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Testing (rspec)''' ==&lt;br /&gt;
&lt;br /&gt;
==== '''save_advice''' ====&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154764</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154764"/>
		<updated>2024-04-04T07:48:59Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Project Description */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller. &lt;br /&gt;
&lt;br /&gt;
Advice_controller first checks whether current user has TA privileges or not by implementing action_allowed? method. Secondly it sets the number of advices based on score and sort it in descending order. Then it checks four conditions for the advices.&lt;br /&gt;
&lt;br /&gt;
Methods &lt;br /&gt;
* action_allowed?&lt;br /&gt;
* invalid_advice?&lt;br /&gt;
* edit_advice&lt;br /&gt;
* save_advice&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154763</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154763"/>
		<updated>2024-04-04T07:46:14Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Project Description''' ==&lt;br /&gt;
This project involves implementing the AdviceController into the reimplementation repository. The controller has limited functionality where it performs validation checks on advice. We will also be making a few refactoring changes and ensuring full test coverage of the methods on the controller.&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_Refactor_Advice_Controller&amp;diff=154759</id>
		<title>CSC/ECE 517 Spring 2024 - Refactor Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_Refactor_Advice_Controller&amp;diff=154759"/>
		<updated>2024-04-02T21:38:14Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: Bdevine2 moved page CSC/ECE 517 Spring 2024 - Refactor Advice Controller to CSC/ECE 517 Spring 2024 - E2422 Refactor Advice Controller: moving to rename&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[CSC/ECE 517 Spring 2024 - E2422 Refactor Advice Controller]]&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154758</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154758"/>
		<updated>2024-04-02T21:38:14Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: Bdevine2 moved page CSC/ECE 517 Spring 2024 - Refactor Advice Controller to CSC/ECE 517 Spring 2024 - E2422 Refactor Advice Controller: moving to rename&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154757</id>
		<title>CSC/ECE 517 Spring 2024 - E2447. Reimplement Advice Controller</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2024_-_E2447._Reimplement_Advice_Controller&amp;diff=154757"/>
		<updated>2024-03-31T21:45:17Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: Created page with &amp;quot;== '''About Expertiza''' == [https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subje...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== '''About Expertiza''' ==&lt;br /&gt;
[https://expertiza.ncsu.edu/ Expertiza] is a software which benefits both instructors and students by providing platform for various types of submissions and providing reusable objects for peer review. Expertiza is an open-source project developed on [https://rubyonrails.org/ Ruby on Rails] framework. In Expertiza an instructors can not only create and customize new or existing assignments, but he/she can also create a list of topics and subject in which the students can sign up. Along with that, Students can form their teams and groups to work with on various projects and assignments. Students can also peer-review other students' submissions. This enables students to work together to improve each other’s learning experiences by providing feedbacks.&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=154717</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=154717"/>
		<updated>2024-03-27T09:11:12Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The validate_questionnaire method was changed in order to follow the Single Responsibility Principle in the SOLID guidelines. Before refactoring, validate_questionnaire was responsible for checking max, min, and if the min was less than max. So, after refactoring, the methods are split up to accomplish one check in each method. This also helps the readability of the code as the checks are very clear.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. No direct changes were made to this method and the method was left as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
There was no reason to do an each on the list if the first one was going to throw an exception already so that was altered to just get first.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, comments were added to improve the readability of the code. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method handles copying a questionnaire and its attributes and dependencies in the model structure. We find the questionnaire to copy from the database and assign it to orig_questionnaire. In the original code, they used a questions variable to access different questions associated with the original questionnaire. However, we can directly access the questions associated with a questionnaire with how the models are set up. So, it has been eliminated when reimplementing the method. String interpolation was used instead of string concatenation to improve readability and simplicity. It is also less error-prone when compared to concatenation. A big change in the implementation of this method is wrapping the copying process in a transaction. This is done to ensure data consistency. Active Record Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. (Ref. https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)  We started an ActiveRecord transaction to save this new questionnaire and to duplicate the questions associated with the original questionnaire.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests.&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these the test the skeleton provided was used and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It is important to test that deleting a questionnaire also deletes all the questions associated with that questionnaire. In this case we test two situations, first we need to test just one question on the questionnaire, and then multiple questions on the questionnaire.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the last parts of delete testing I used 2 more of the test skeletons.  There are two scenarios to test in relation to the question node associated to a questionnaire.  We should assert that questions are deleted with and without an associated node, and that the questionnaire node does not exist any longer in the case where there was an associated node. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[File:Copy_questionnaire_test.png]]&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
The tests for this method were implemented correctly and they covered most of the functionalities of this particular function. &lt;br /&gt;
&lt;br /&gt;
The first set of tests checks the method calls from copy_questionnaire_details. The first line stubs the 'find' method of the Questionnaire model. The second line stubs the 'where' method of the Questionnaire model. The first test stubs the find method to return a mock 'questionnaire' object when called with argument 1. The second test looks for an array containing a 'question' object when called with questionnaire ID as an argument. &lt;br /&gt;
&lt;br /&gt;
The second set of tests checks the copying process of the copy_questionnaire_details method. In the original code, they had two different tests for it. One is to see if the new questionnaire is saved in the database and a separate test is used to check for proper associations of questions in the new questionnaire. We have combined these tests to avoid rerunning the same tests to retrieve migration data from the database. Our tests cover almost all the functionalities provided in the suggested skeleton for the test files. &lt;br /&gt;
&lt;br /&gt;
The first line of the second set of tests checks if the method yields 'question1' and 'question2' when questions.each is called, checking the associations between the 'questionnaire' model and the 'question' model. &lt;br /&gt;
&lt;br /&gt;
We are checking the consistencies of the copied data by comparing instructor ID, copied questionnaire name, and time of creation/copying. We are also checking the associations of the copied questionnaire with the 'question' model by checking the count of these copied questions and each particular question file by comparing its file type and ID. &lt;br /&gt;
&lt;br /&gt;
Overall, these tests ensure that the copy_questionnaire_details method correctly creates a copy of a given questionnaire, including its associated questions, and that certain method calls are made within the method.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== The below tests were redundant ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
These two tests were part of the test skeleton but were redundant, so we did not include them.&lt;br /&gt;
&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== SimpleCov Coverage Report ===&lt;br /&gt;
----&lt;br /&gt;
The SimpleCov report shows 96.23% relevant LoC for our questionnaire.rb model file. &lt;br /&gt;
&lt;br /&gt;
*'''https://adryne01.github.io/Prog3-SimpleCov/Code%20coverage%20for%20Reimplementation-back-end.html'''&lt;br /&gt;
----&lt;br /&gt;
[[File:SimpleCov_Report.png]]&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=154715</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=154715"/>
		<updated>2024-03-27T09:09:48Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The validate_questionnaire method was changed in order to follow the Single Responsibility Principle in the SOLID guidelines. Before refactoring, validate_questionnaire was responsible for checking max, min, and if the min was less than max. So, after refactoring, the methods are split up to accomplish one check in each method. This also helps the readability of the code as the checks are very clear.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. No direct changes were made to this method and the method was left as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
There was no reason to do an each on the list if the first one was going to throw an exception already so that was altered to just get first.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, comments were added to improve the readability of the code. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method handles copying a questionnaire and its attributes and dependencies in the model structure. We find the questionnaire to copy from the database and assign it to orig_questionnaire. In the original code, they used a questions variable to access different questions associated with the original questionnaire. However, we can directly access the questions associated with a questionnaire with how the models are set up. So, it has been eliminated when reimplementing the method. String interpolation was used instead of string concatenation to improve readability and simplicity. It is also less error-prone when compared to concatenation. A big change in the implementation of this method is wrapping the copying process in a transaction. This is done to ensure data consistency. Active Record Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. (Ref. https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)  We started an ActiveRecord transaction to save this new questionnaire and to duplicate the questions associated with the original questionnaire.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests.&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these the test the skeleton provided was used and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It is important to test that deleting a questionnaire also deletes all the questions associated with that questionnaire. In this case we test two situations, first we need to test just one question on the questionnaire, and then multiple questions on the questionnaire.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the last parts of delete testing I used 2 more of the test skeletons.  There are two scenarios to test in relation to the question node associated to a questionnaire.  We should assert that questions are deleted with and without an associated node, and that the questionnaire node does not exist any longer in the case where there was an associated node. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[File:Copy_questionnaire_test.png]]&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
The tests for this method were implemented correctly and they covered most of the functionalities of this particular function. &lt;br /&gt;
&lt;br /&gt;
The first set of tests checks the method calls from copy_questionnaire_details. The first line stubs the 'find' method of the Questionnaire model. The second line stubs the 'where' method of the Questionnaire model. The first test stubs the find method to return a mock 'questionnaire' object when called with argument 1. The second test looks for an array containing a 'question' object when called with questionnaire ID as an argument. &lt;br /&gt;
&lt;br /&gt;
The second set of tests checks the copying process of the copy_questionnaire_details method. In the original code, they had two different tests for it. One is to see if the new questionnaire is saved in the database and a separate test is used to check for proper associations of questions in the new questionnaire. We have combined these tests to avoid rerunning the same tests to retrieve migration data from the database. Our tests cover almost all the functionalities provided in the suggested skeleton for the test files. &lt;br /&gt;
&lt;br /&gt;
The first line of the second set of tests checks if the method yields 'question1' and 'question2' when questions.each is called, checking the associations between the 'questionnaire' model and the 'question' model. &lt;br /&gt;
&lt;br /&gt;
We are checking the consistencies of the copied data by comparing instructor ID, copied questionnaire name, and time of creation/copying. We are also checking the associations of the copied questionnaire with the 'question' model by checking the count of these copied questions and each particular question file by comparing its file type and ID. &lt;br /&gt;
&lt;br /&gt;
Overall, these tests ensure that the copy_questionnaire_details method correctly creates a copy of a given questionnaire, including its associated questions, and that certain method calls are made within the method.&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== SimpleCov Coverage Report ===&lt;br /&gt;
----&lt;br /&gt;
The SimpleCov report shows 96.23% relevant LoC for our questionnaire.rb model file. &lt;br /&gt;
&lt;br /&gt;
*'''https://adryne01.github.io/Prog3-SimpleCov/Code%20coverage%20for%20Reimplementation-back-end.html'''&lt;br /&gt;
----&lt;br /&gt;
[[File:SimpleCov_Report.png]]&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=154713</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=154713"/>
		<updated>2024-03-27T09:08:55Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* true_false_questions? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The validate_questionnaire method was changed in order to follow the Single Responsibility Principle in the SOLID guidelines. Before refactoring, validate_questionnaire was responsible for checking max, min, and if the min was less than max. So, after refactoring, the methods are split up to accomplish one check in each method. This also helps the readability of the code as the checks are very clear.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. No direct changes were made to this method and the method was left as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
There was no reason to do an each on the list if the first one was going to throw an exception already so that was altered to just get first.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, comments were added to improve the readability of the code. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method handles copying a questionnaire and its attributes and dependencies in the model structure. We find the questionnaire to copy from the database and assign it to orig_questionnaire. In the original code, they used a questions variable to access different questions associated with the original questionnaire. However, we can directly access the questions associated with a questionnaire with how the models are set up. So, it has been eliminated when reimplementing the method. String interpolation was used instead of string concatenation to improve readability and simplicity. It is also less error-prone when compared to concatenation. A big change in the implementation of this method is wrapping the copying process in a transaction. This is done to ensure data consistency. Active Record Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. (Ref. https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)  We started an ActiveRecord transaction to save this new questionnaire and to duplicate the questions associated with the original questionnaire.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests.&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these the test the skeleton provided was used and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It is important to test that deleting a questionnaire also deletes all the questions associated with that questionnaire. In this case we test two situations, first we need to test just one question on the questionnaire, and then multiple questions on the questionnaire.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the last parts of delete testing I used 2 more of the test skeletons.  There are two scenarios to test in relation to the question node associated to a questionnaire.  We should assert that questions are deleted with and without an associated node, and that the questionnaire node does not exist any longer in the case where there was an associated node. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[File:Copy_questionnaire_test.png]]&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
The tests for this method were implemented correctly and they covered most of the functionalities of this particular function. &lt;br /&gt;
&lt;br /&gt;
The first set of tests checks the method calls from copy_questionnaire_details. The first line stubs the 'find' method of the Questionnaire model. The second line stubs the 'where' method of the Questionnaire model. The first test stubs the find method to return a mock 'questionnaire' object when called with argument 1. The second test looks for an array containing a 'question' object when called with questionnaire ID as an argument. &lt;br /&gt;
&lt;br /&gt;
The second set of tests checks the copying process of the copy_questionnaire_details method. In the original code, they had two different tests for it. One is to see if the new questionnaire is saved in the database and a separate test is used to check for proper associations of questions in the new questionnaire. We have combined these tests to avoid rerunning the same tests to retrieve migration data from the database. Our tests cover almost all the functionalities provided in the suggested skeleton for the test files. &lt;br /&gt;
&lt;br /&gt;
The first line of the second set of tests checks if the method yields 'question1' and 'question2' when questions.each is called, checking the associations between the 'questionnaire' model and the 'question' model. &lt;br /&gt;
&lt;br /&gt;
We are checking the consistencies of the copied data by comparing instructor ID, copied questionnaire name, and time of creation/copying. We are also checking the associations of the copied questionnaire with the 'question' model by checking the count of these copied questions and each particular question file by comparing its file type and ID. &lt;br /&gt;
&lt;br /&gt;
Overall, these tests ensure that the copy_questionnaire_details method correctly creates a copy of a given questionnaire, including its associated questions, and that certain method calls are made within the method.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== SimpleCov Coverage Report ===&lt;br /&gt;
----&lt;br /&gt;
The SimpleCov report shows 96.23% relevant LoC for our questionnaire.rb model file. &lt;br /&gt;
&lt;br /&gt;
*'''https://adryne01.github.io/Prog3-SimpleCov/Code%20coverage%20for%20Reimplementation-back-end.html'''&lt;br /&gt;
----&lt;br /&gt;
[[File:SimpleCov_Report.png]]&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=154711</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=154711"/>
		<updated>2024-03-27T09:07:39Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* delete */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The validate_questionnaire method was changed in order to follow the Single Responsibility Principle in the SOLID guidelines. Before refactoring, validate_questionnaire was responsible for checking max, min, and if the min was less than max. So, after refactoring, the methods are split up to accomplish one check in each method. This also helps the readability of the code as the checks are very clear.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. No direct changes were made to this method and the method was left as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
There was no reason to do an each on the list if the first one was going to throw an exception already so that was altered to just get first.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, comments were added to improve the readability of the code. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method handles copying a questionnaire and its attributes and dependencies in the model structure. We find the questionnaire to copy from the database and assign it to orig_questionnaire. In the original code, they used a questions variable to access different questions associated with the original questionnaire. However, we can directly access the questions associated with a questionnaire with how the models are set up. So, it has been eliminated when reimplementing the method. String interpolation was used instead of string concatenation to improve readability and simplicity. It is also less error-prone when compared to concatenation. A big change in the implementation of this method is wrapping the copying process in a transaction. This is done to ensure data consistency. Active Record Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. (Ref. https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)  We started an ActiveRecord transaction to save this new questionnaire and to duplicate the questions associated with the original questionnaire.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests.&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these tests I did use exclusively the test skeleton provided to us and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It is important to test that deleting a questionnaire also deletes all the questions associated with that questionnaire. In this case we test two situations, first we need to test just one question on the questionnaire, and then multiple questions on the questionnaire.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the last parts of delete testing I used 2 more of the test skeletons.  There are two scenarios to test in relation to the question node associated to a questionnaire.  We should assert that questions are deleted with and without an associated node, and that the questionnaire node does not exist any longer in the case where there was an associated node. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[File:Copy_questionnaire_test.png]]&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
The tests for this method were implemented correctly and they covered most of the functionalities of this particular function. &lt;br /&gt;
&lt;br /&gt;
The first set of tests checks the method calls from copy_questionnaire_details. The first line stubs the 'find' method of the Questionnaire model. The second line stubs the 'where' method of the Questionnaire model. The first test stubs the find method to return a mock 'questionnaire' object when called with argument 1. The second test looks for an array containing a 'question' object when called with questionnaire ID as an argument. &lt;br /&gt;
&lt;br /&gt;
The second set of tests checks the copying process of the copy_questionnaire_details method. In the original code, they had two different tests for it. One is to see if the new questionnaire is saved in the database and a separate test is used to check for proper associations of questions in the new questionnaire. We have combined these tests to avoid rerunning the same tests to retrieve migration data from the database. Our tests cover almost all the functionalities provided in the suggested skeleton for the test files. &lt;br /&gt;
&lt;br /&gt;
The first line of the second set of tests checks if the method yields 'question1' and 'question2' when questions.each is called, checking the associations between the 'questionnaire' model and the 'question' model. &lt;br /&gt;
&lt;br /&gt;
We are checking the consistencies of the copied data by comparing instructor ID, copied questionnaire name, and time of creation/copying. We are also checking the associations of the copied questionnaire with the 'question' model by checking the count of these copied questions and each particular question file by comparing its file type and ID. &lt;br /&gt;
&lt;br /&gt;
Overall, these tests ensure that the copy_questionnaire_details method correctly creates a copy of a given questionnaire, including its associated questions, and that certain method calls are made within the method.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== SimpleCov Coverage Report ===&lt;br /&gt;
----&lt;br /&gt;
The SimpleCov report shows 96.23% relevant LoC for our questionnaire.rb model file. &lt;br /&gt;
&lt;br /&gt;
*'''https://adryne01.github.io/Prog3-SimpleCov/Code%20coverage%20for%20Reimplementation-back-end.html'''&lt;br /&gt;
----&lt;br /&gt;
[[File:SimpleCov_Report.png]]&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
== Authors ==&lt;br /&gt;
This project was completed by Aiden Bartlett, Brandon Devine, and Aditya Karthikeyan.&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153084</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153084"/>
		<updated>2024-03-23T14:50:10Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* delete */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I changed the validate_questionnaire from a method that needs to be called for validation into individual validates in the order I expected so they could each have single responsibility.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. I made no direct changes to this method and left it as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I did not have to alter this method very much.  First of all there was no reason to do an each on the list if the first one was going to throw an exception already, so I simplified that.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, we added comments to make it very clear what the logic was doing. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I have removed the unnecessary assignment of questions variable since we can directly access questions associated with the original questionnaire. I used string interpolation, instead of concatenation, for setting the name of the copied questionnaire. I wrapped the copying process in a transaction to ensure data consistency. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests.&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these tests I did use exclusively the test skeleton provided to us and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It is important to test that deleting a questionnaire also deletes all the questions associated with that questionnaire. In this case we test two situations, first we need to test just one question on the questionnaire, and then multiple questions on the questionnaire.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the last parts of delete testing I used 2 more of the test skeletons.  There are two scenarios to test in relation to the question node associated to a questionnaire.  We should assert that questions are deleted with and without an associated node, and that the questionnaire node does not exist any longer in the case where there was an associated node. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
 describe '.copy_questionnaire_details' do&lt;br /&gt;
    # Test ensures calls from the method copy_questionnaire_details&lt;br /&gt;
    it 'allowing calls from copy_questionnaire_details' do&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([Question])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    # Test ensures creation of a copy of given questionnaire&lt;br /&gt;
    # Combined two tests into one to avoid performing the same functionalities again to test another feature.&lt;br /&gt;
    it 'creates a copy of the questionnaire' do&lt;br /&gt;
      instructor.save!&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      question1.save!&lt;br /&gt;
      question2.save!&lt;br /&gt;
&lt;br /&gt;
      # Stub the where method to return the questions associated with the original questionnaire&lt;br /&gt;
      allow(questionnaire).to receive_message_chain(:questions, :each).and_yield(question1).and_yield(question2)&lt;br /&gt;
&lt;br /&gt;
      # Call the copy_questionnaire_details method&lt;br /&gt;
      copied_questionnaire = Questionnaire.copy_questionnaire_details({ id: questionnaire.id })&lt;br /&gt;
&lt;br /&gt;
      # Assertions&lt;br /&gt;
      expect(copied_questionnaire.instructor_id).to eq(questionnaire.instructor_id)&lt;br /&gt;
      expect(copied_questionnaire.name).to eq(&amp;quot;Copy of #{questionnaire.name}&amp;quot;)&lt;br /&gt;
      expect(copied_questionnaire.created_at).to be_within(1.second).of(Time.zone.now)&lt;br /&gt;
&lt;br /&gt;
      # Verify that the copied questionnaire has associated questions&lt;br /&gt;
      expect(copied_questionnaire.questions.count).to eq(2)&lt;br /&gt;
      expect(copied_questionnaire.questions.first.txt).to eq(question1.txt)&lt;br /&gt;
      expect(copied_questionnaire.questions.second.txt).to eq(question2.txt)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153083</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153083"/>
		<updated>2024-03-23T14:43:20Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* delete */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I changed the validate_questionnaire from a method that needs to be called for validation into individual validates in the order I expected so they could each have single responsibility.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. I made no direct changes to this method and left it as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I did not have to alter this method very much.  First of all there was no reason to do an each on the list if the first one was going to throw an exception already, so I simplified that.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, we added comments to make it very clear what the logic was doing. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I have removed the unnecessary assignment of questions variable since we can directly access questions associated with the original questionnaire. I used string interpolation, instead of concatenation, for setting the name of the copied questionnaire. I wrapped the copying process in a transaction to ensure data consistency. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests.&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these tests I did use exclusively the test skeleton provided to us and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
[[File:Delete-rspec-2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the last parts of delete testing I used 2 more of the test skeletons.  There are two scenarios to test in relation to the question node associated to a questionnaire.  We should assert that questions are deleted with and without an associated node, and that the questionnaire node does not exist any longer in the case where there was an associated node. &lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
 describe '.copy_questionnaire_details' do&lt;br /&gt;
    # Test ensures calls from the method copy_questionnaire_details&lt;br /&gt;
    it 'allowing calls from copy_questionnaire_details' do&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([Question])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    # Test ensures creation of a copy of given questionnaire&lt;br /&gt;
    # Combined two tests into one to avoid performing the same functionalities again to test another feature.&lt;br /&gt;
    it 'creates a copy of the questionnaire' do&lt;br /&gt;
      instructor.save!&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      question1.save!&lt;br /&gt;
      question2.save!&lt;br /&gt;
&lt;br /&gt;
      # Stub the where method to return the questions associated with the original questionnaire&lt;br /&gt;
      allow(questionnaire).to receive_message_chain(:questions, :each).and_yield(question1).and_yield(question2)&lt;br /&gt;
&lt;br /&gt;
      # Call the copy_questionnaire_details method&lt;br /&gt;
      copied_questionnaire = Questionnaire.copy_questionnaire_details({ id: questionnaire.id })&lt;br /&gt;
&lt;br /&gt;
      # Assertions&lt;br /&gt;
      expect(copied_questionnaire.instructor_id).to eq(questionnaire.instructor_id)&lt;br /&gt;
      expect(copied_questionnaire.name).to eq(&amp;quot;Copy of #{questionnaire.name}&amp;quot;)&lt;br /&gt;
      expect(copied_questionnaire.created_at).to be_within(1.second).of(Time.zone.now)&lt;br /&gt;
&lt;br /&gt;
      # Verify that the copied questionnaire has associated questions&lt;br /&gt;
      expect(copied_questionnaire.questions.count).to eq(2)&lt;br /&gt;
      expect(copied_questionnaire.questions.first.txt).to eq(question1.txt)&lt;br /&gt;
      expect(copied_questionnaire.questions.second.txt).to eq(question2.txt)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153082</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153082"/>
		<updated>2024-03-23T14:39:45Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* delete */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I changed the validate_questionnaire from a method that needs to be called for validation into individual validates in the order I expected so they could each have single responsibility.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. I made no direct changes to this method and left it as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I did not have to alter this method very much.  First of all there was no reason to do an each on the list if the first one was going to throw an exception already, so I simplified that.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, we added comments to make it very clear what the logic was doing. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I have removed the unnecessary assignment of questions variable since we can directly access questions associated with the original questionnaire. I used string interpolation, instead of concatenation, for setting the name of the copied questionnaire. I wrapped the copying process in a transaction to ensure data consistency. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests.&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these tests I did use exclusively the test skeleton provided to us and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
[[File:Delete-rspec-2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
[[File:Delete-rspec-3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
 describe '.copy_questionnaire_details' do&lt;br /&gt;
    # Test ensures calls from the method copy_questionnaire_details&lt;br /&gt;
    it 'allowing calls from copy_questionnaire_details' do&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([Question])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    # Test ensures creation of a copy of given questionnaire&lt;br /&gt;
    # Combined two tests into one to avoid performing the same functionalities again to test another feature.&lt;br /&gt;
    it 'creates a copy of the questionnaire' do&lt;br /&gt;
      instructor.save!&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      question1.save!&lt;br /&gt;
      question2.save!&lt;br /&gt;
&lt;br /&gt;
      # Stub the where method to return the questions associated with the original questionnaire&lt;br /&gt;
      allow(questionnaire).to receive_message_chain(:questions, :each).and_yield(question1).and_yield(question2)&lt;br /&gt;
&lt;br /&gt;
      # Call the copy_questionnaire_details method&lt;br /&gt;
      copied_questionnaire = Questionnaire.copy_questionnaire_details({ id: questionnaire.id })&lt;br /&gt;
&lt;br /&gt;
      # Assertions&lt;br /&gt;
      expect(copied_questionnaire.instructor_id).to eq(questionnaire.instructor_id)&lt;br /&gt;
      expect(copied_questionnaire.name).to eq(&amp;quot;Copy of #{questionnaire.name}&amp;quot;)&lt;br /&gt;
      expect(copied_questionnaire.created_at).to be_within(1.second).of(Time.zone.now)&lt;br /&gt;
&lt;br /&gt;
      # Verify that the copied questionnaire has associated questions&lt;br /&gt;
      expect(copied_questionnaire.questions.count).to eq(2)&lt;br /&gt;
      expect(copied_questionnaire.questions.first.txt).to eq(question1.txt)&lt;br /&gt;
      expect(copied_questionnaire.questions.second.txt).to eq(question2.txt)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153081</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153081"/>
		<updated>2024-03-23T14:38:53Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* delete */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I changed the validate_questionnaire from a method that needs to be called for validation into individual validates in the order I expected so they could each have single responsibility.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. I made no direct changes to this method and left it as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I did not have to alter this method very much.  First of all there was no reason to do an each on the list if the first one was going to throw an exception already, so I simplified that.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, we added comments to make it very clear what the logic was doing. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I have removed the unnecessary assignment of questions variable since we can directly access questions associated with the original questionnaire. I used string interpolation, instead of concatenation, for setting the name of the copied questionnaire. I wrapped the copying process in a transaction to ensure data consistency. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests.&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these tests I did use exclusively the test skeleton provided to us and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Delete-rspec-1.png]]&lt;br /&gt;
[[File:Delete-rspec-2.png]]&lt;br /&gt;
[[File:Delete-rspec-3.png]]&lt;br /&gt;
&lt;br /&gt;
  describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
    context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_single_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        single_assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: single_assignment.id, questionnaire_id: questionnaire_single_assignment.id})&lt;br /&gt;
        questionnaire_multi_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        multi_assignment1 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment1.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        multi_assignment2 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment2.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_single_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(/^The assignment .* uses this questionnaire/)&lt;br /&gt;
        end&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        assignment1_pattern = /^The assignment #{Regexp.escape(multi_assignment1.name)} uses this questionnaire/&lt;br /&gt;
        assignment2_pattern = /^The assignment #{Regexp.escape(multi_assignment2.name)} uses this questionnaire/&lt;br /&gt;
        # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment1_pattern)&lt;br /&gt;
        end&lt;br /&gt;
        multi_assignment1.destroy!&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment2_pattern)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_one_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_one_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_one_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_multi_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_multi_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        question2 = questionnaire_multi_question.questions.build(weight: 1, id: 2, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question2.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_multi_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        expect(Question.find_by(id: question2.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
      it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: The questionnaire node is deleted&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        q_node = QuestionnaireNode.create(parent_id: 0, node_object_id: questionnaire.id, type: 'QuestionnaireNode')&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(QuestionnaireNode.find_by(id: q_node.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: No error is raised and the method completes successfully&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect { questionnaire.delete }.not_to raise_error&lt;br /&gt;
        expect(Questionnaire.find_by(id: questionnaire.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
 describe '.copy_questionnaire_details' do&lt;br /&gt;
    # Test ensures calls from the method copy_questionnaire_details&lt;br /&gt;
    it 'allowing calls from copy_questionnaire_details' do&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([Question])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    # Test ensures creation of a copy of given questionnaire&lt;br /&gt;
    # Combined two tests into one to avoid performing the same functionalities again to test another feature.&lt;br /&gt;
    it 'creates a copy of the questionnaire' do&lt;br /&gt;
      instructor.save!&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      question1.save!&lt;br /&gt;
      question2.save!&lt;br /&gt;
&lt;br /&gt;
      # Stub the where method to return the questions associated with the original questionnaire&lt;br /&gt;
      allow(questionnaire).to receive_message_chain(:questions, :each).and_yield(question1).and_yield(question2)&lt;br /&gt;
&lt;br /&gt;
      # Call the copy_questionnaire_details method&lt;br /&gt;
      copied_questionnaire = Questionnaire.copy_questionnaire_details({ id: questionnaire.id })&lt;br /&gt;
&lt;br /&gt;
      # Assertions&lt;br /&gt;
      expect(copied_questionnaire.instructor_id).to eq(questionnaire.instructor_id)&lt;br /&gt;
      expect(copied_questionnaire.name).to eq(&amp;quot;Copy of #{questionnaire.name}&amp;quot;)&lt;br /&gt;
      expect(copied_questionnaire.created_at).to be_within(1.second).of(Time.zone.now)&lt;br /&gt;
&lt;br /&gt;
      # Verify that the copied questionnaire has associated questions&lt;br /&gt;
      expect(copied_questionnaire.questions.count).to eq(2)&lt;br /&gt;
      expect(copied_questionnaire.questions.first.txt).to eq(question1.txt)&lt;br /&gt;
      expect(copied_questionnaire.questions.second.txt).to eq(question2.txt)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Delete-rspec-3.png&amp;diff=153080</id>
		<title>File:Delete-rspec-3.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Delete-rspec-3.png&amp;diff=153080"/>
		<updated>2024-03-23T14:37:54Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Delete-rspec-2.png&amp;diff=153079</id>
		<title>File:Delete-rspec-2.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Delete-rspec-2.png&amp;diff=153079"/>
		<updated>2024-03-23T14:37:29Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Delete-rspec-1.png&amp;diff=153078</id>
		<title>File:Delete-rspec-1.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Delete-rspec-1.png&amp;diff=153078"/>
		<updated>2024-03-23T14:37:06Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: first section of delete tests&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
first section of delete tests&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153077</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153077"/>
		<updated>2024-03-23T14:33:58Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Questionnaire model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I changed the validate_questionnaire from a method that needs to be called for validation into individual validates in the order I expected so they could each have single responsibility.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. I made no direct changes to this method and left it as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I did not have to alter this method very much.  First of all there was no reason to do an each on the list if the first one was going to throw an exception already, so I simplified that.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, we added comments to make it very clear what the logic was doing. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I have removed the unnecessary assignment of questions variable since we can directly access questions associated with the original questionnaire. I used string interpolation, instead of concatenation, for setting the name of the copied questionnaire. I wrapped the copying process in a transaction to ensure data consistency. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests.&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these tests I did use exclusively the test skeleton provided to us and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
  describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
    context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_single_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        single_assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: single_assignment.id, questionnaire_id: questionnaire_single_assignment.id})&lt;br /&gt;
        questionnaire_multi_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        multi_assignment1 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment1.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        multi_assignment2 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment2.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_single_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(/^The assignment .* uses this questionnaire/)&lt;br /&gt;
        end&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        assignment1_pattern = /^The assignment #{Regexp.escape(multi_assignment1.name)} uses this questionnaire/&lt;br /&gt;
        assignment2_pattern = /^The assignment #{Regexp.escape(multi_assignment2.name)} uses this questionnaire/&lt;br /&gt;
        # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment1_pattern)&lt;br /&gt;
        end&lt;br /&gt;
        multi_assignment1.destroy!&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment2_pattern)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_one_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_one_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_one_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_multi_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_multi_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        question2 = questionnaire_multi_question.questions.build(weight: 1, id: 2, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question2.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_multi_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        expect(Question.find_by(id: question2.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
      it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: The questionnaire node is deleted&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        q_node = QuestionnaireNode.create(parent_id: 0, node_object_id: questionnaire.id, type: 'QuestionnaireNode')&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(QuestionnaireNode.find_by(id: q_node.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: No error is raised and the method completes successfully&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect { questionnaire.delete }.not_to raise_error&lt;br /&gt;
        expect(Questionnaire.find_by(id: questionnaire.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
 describe '.copy_questionnaire_details' do&lt;br /&gt;
    # Test ensures calls from the method copy_questionnaire_details&lt;br /&gt;
    it 'allowing calls from copy_questionnaire_details' do&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([Question])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    # Test ensures creation of a copy of given questionnaire&lt;br /&gt;
    # Combined two tests into one to avoid performing the same functionalities again to test another feature.&lt;br /&gt;
    it 'creates a copy of the questionnaire' do&lt;br /&gt;
      instructor.save!&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      question1.save!&lt;br /&gt;
      question2.save!&lt;br /&gt;
&lt;br /&gt;
      # Stub the where method to return the questions associated with the original questionnaire&lt;br /&gt;
      allow(questionnaire).to receive_message_chain(:questions, :each).and_yield(question1).and_yield(question2)&lt;br /&gt;
&lt;br /&gt;
      # Call the copy_questionnaire_details method&lt;br /&gt;
      copied_questionnaire = Questionnaire.copy_questionnaire_details({ id: questionnaire.id })&lt;br /&gt;
&lt;br /&gt;
      # Assertions&lt;br /&gt;
      expect(copied_questionnaire.instructor_id).to eq(questionnaire.instructor_id)&lt;br /&gt;
      expect(copied_questionnaire.name).to eq(&amp;quot;Copy of #{questionnaire.name}&amp;quot;)&lt;br /&gt;
      expect(copied_questionnaire.created_at).to be_within(1.second).of(Time.zone.now)&lt;br /&gt;
&lt;br /&gt;
      # Verify that the copied questionnaire has associated questions&lt;br /&gt;
      expect(copied_questionnaire.questions.count).to eq(2)&lt;br /&gt;
      expect(copied_questionnaire.questions.first.txt).to eq(question1.txt)&lt;br /&gt;
      expect(copied_questionnaire.questions.second.txt).to eq(question2.txt)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153076</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153076"/>
		<updated>2024-03-23T14:33:41Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Questionnaire model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I changed the validate_questionnaire from a method that needs to be called for validation into individual validates in the order I expected so they could each have single responsibility.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. I made no direct changes to this method and left it as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I did not have to alter this method very much.  First of all there was no reason to do an each on the list if the first one was going to throw an exception already, so I simplified that.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, we added comments to make it very clear what the logic was doing. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I have removed the unnecessary assignment of questions variable since we can directly access questions associated with the original questionnaire. I used string interpolation, instead of concatenation, for setting the name of the copied questionnaire. I wrapped the copying process in a transaction to ensure data consistency. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Some of the variables that we will need in all or nearly all tests we have declared at the top within the whole describe for the Questionnaire class tests&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these tests I did use exclusively the test skeleton provided to us and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
  describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
    context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_single_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        single_assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: single_assignment.id, questionnaire_id: questionnaire_single_assignment.id})&lt;br /&gt;
        questionnaire_multi_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        multi_assignment1 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment1.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        multi_assignment2 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment2.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_single_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(/^The assignment .* uses this questionnaire/)&lt;br /&gt;
        end&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        assignment1_pattern = /^The assignment #{Regexp.escape(multi_assignment1.name)} uses this questionnaire/&lt;br /&gt;
        assignment2_pattern = /^The assignment #{Regexp.escape(multi_assignment2.name)} uses this questionnaire/&lt;br /&gt;
        # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment1_pattern)&lt;br /&gt;
        end&lt;br /&gt;
        multi_assignment1.destroy!&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment2_pattern)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_one_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_one_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_one_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_multi_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_multi_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        question2 = questionnaire_multi_question.questions.build(weight: 1, id: 2, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question2.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_multi_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        expect(Question.find_by(id: question2.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
      it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: The questionnaire node is deleted&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        q_node = QuestionnaireNode.create(parent_id: 0, node_object_id: questionnaire.id, type: 'QuestionnaireNode')&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(QuestionnaireNode.find_by(id: q_node.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: No error is raised and the method completes successfully&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect { questionnaire.delete }.not_to raise_error&lt;br /&gt;
        expect(Questionnaire.find_by(id: questionnaire.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
 describe '.copy_questionnaire_details' do&lt;br /&gt;
    # Test ensures calls from the method copy_questionnaire_details&lt;br /&gt;
    it 'allowing calls from copy_questionnaire_details' do&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([Question])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    # Test ensures creation of a copy of given questionnaire&lt;br /&gt;
    # Combined two tests into one to avoid performing the same functionalities again to test another feature.&lt;br /&gt;
    it 'creates a copy of the questionnaire' do&lt;br /&gt;
      instructor.save!&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      question1.save!&lt;br /&gt;
      question2.save!&lt;br /&gt;
&lt;br /&gt;
      # Stub the where method to return the questions associated with the original questionnaire&lt;br /&gt;
      allow(questionnaire).to receive_message_chain(:questions, :each).and_yield(question1).and_yield(question2)&lt;br /&gt;
&lt;br /&gt;
      # Call the copy_questionnaire_details method&lt;br /&gt;
      copied_questionnaire = Questionnaire.copy_questionnaire_details({ id: questionnaire.id })&lt;br /&gt;
&lt;br /&gt;
      # Assertions&lt;br /&gt;
      expect(copied_questionnaire.instructor_id).to eq(questionnaire.instructor_id)&lt;br /&gt;
      expect(copied_questionnaire.name).to eq(&amp;quot;Copy of #{questionnaire.name}&amp;quot;)&lt;br /&gt;
      expect(copied_questionnaire.created_at).to be_within(1.second).of(Time.zone.now)&lt;br /&gt;
&lt;br /&gt;
      # Verify that the copied questionnaire has associated questions&lt;br /&gt;
      expect(copied_questionnaire.questions.count).to eq(2)&lt;br /&gt;
      expect(copied_questionnaire.questions.first.txt).to eq(question1.txt)&lt;br /&gt;
      expect(copied_questionnaire.questions.second.txt).to eq(question2.txt)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153075</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153075"/>
		<updated>2024-03-23T14:32:28Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Rspec Testing */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I changed the validate_questionnaire from a method that needs to be called for validation into individual validates in the order I expected so they could each have single responsibility.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. I made no direct changes to this method and left it as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I did not have to alter this method very much.  First of all there was no reason to do an each on the list if the first one was going to throw an exception already, so I simplified that.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, we added comments to make it very clear what the logic was doing. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I have removed the unnecessary assignment of questions variable since we can directly access questions associated with the original questionnaire. I used string interpolation, instead of concatenation, for setting the name of the copied questionnaire. I wrapped the copying process in a transaction to ensure data consistency. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire model ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Rspec-header.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these tests I did use exclusively the test skeleton provided to us and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
  describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
    context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_single_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        single_assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: single_assignment.id, questionnaire_id: questionnaire_single_assignment.id})&lt;br /&gt;
        questionnaire_multi_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        multi_assignment1 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment1.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        multi_assignment2 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment2.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_single_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(/^The assignment .* uses this questionnaire/)&lt;br /&gt;
        end&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        assignment1_pattern = /^The assignment #{Regexp.escape(multi_assignment1.name)} uses this questionnaire/&lt;br /&gt;
        assignment2_pattern = /^The assignment #{Regexp.escape(multi_assignment2.name)} uses this questionnaire/&lt;br /&gt;
        # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment1_pattern)&lt;br /&gt;
        end&lt;br /&gt;
        multi_assignment1.destroy!&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment2_pattern)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_one_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_one_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_one_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_multi_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_multi_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        question2 = questionnaire_multi_question.questions.build(weight: 1, id: 2, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question2.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_multi_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        expect(Question.find_by(id: question2.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
      it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: The questionnaire node is deleted&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        q_node = QuestionnaireNode.create(parent_id: 0, node_object_id: questionnaire.id, type: 'QuestionnaireNode')&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(QuestionnaireNode.find_by(id: q_node.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: No error is raised and the method completes successfully&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect { questionnaire.delete }.not_to raise_error&lt;br /&gt;
        expect(Questionnaire.find_by(id: questionnaire.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
 describe '.copy_questionnaire_details' do&lt;br /&gt;
    # Test ensures calls from the method copy_questionnaire_details&lt;br /&gt;
    it 'allowing calls from copy_questionnaire_details' do&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([Question])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    # Test ensures creation of a copy of given questionnaire&lt;br /&gt;
    # Combined two tests into one to avoid performing the same functionalities again to test another feature.&lt;br /&gt;
    it 'creates a copy of the questionnaire' do&lt;br /&gt;
      instructor.save!&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      question1.save!&lt;br /&gt;
      question2.save!&lt;br /&gt;
&lt;br /&gt;
      # Stub the where method to return the questions associated with the original questionnaire&lt;br /&gt;
      allow(questionnaire).to receive_message_chain(:questions, :each).and_yield(question1).and_yield(question2)&lt;br /&gt;
&lt;br /&gt;
      # Call the copy_questionnaire_details method&lt;br /&gt;
      copied_questionnaire = Questionnaire.copy_questionnaire_details({ id: questionnaire.id })&lt;br /&gt;
&lt;br /&gt;
      # Assertions&lt;br /&gt;
      expect(copied_questionnaire.instructor_id).to eq(questionnaire.instructor_id)&lt;br /&gt;
      expect(copied_questionnaire.name).to eq(&amp;quot;Copy of #{questionnaire.name}&amp;quot;)&lt;br /&gt;
      expect(copied_questionnaire.created_at).to be_within(1.second).of(Time.zone.now)&lt;br /&gt;
&lt;br /&gt;
      # Verify that the copied questionnaire has associated questions&lt;br /&gt;
      expect(copied_questionnaire.questions.count).to eq(2)&lt;br /&gt;
      expect(copied_questionnaire.questions.first.txt).to eq(question1.txt)&lt;br /&gt;
      expect(copied_questionnaire.questions.second.txt).to eq(question2.txt)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Rspec-header.png&amp;diff=153074</id>
		<title>File:Rspec-header.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Rspec-header.png&amp;diff=153074"/>
		<updated>2024-03-23T14:29:38Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: the initial let's at the top test class level&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
the initial let's at the top test class level&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153073</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153073"/>
		<updated>2024-03-23T14:26:22Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* true_false_questions? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I changed the validate_questionnaire from a method that needs to be called for validation into individual validates in the order I expected so they could each have single responsibility.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. I made no direct changes to this method and left it as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I did not have to alter this method very much.  First of all there was no reason to do an each on the list if the first one was going to throw an exception already, so I simplified that.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, we added comments to make it very clear what the logic was doing. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I have removed the unnecessary assignment of questions variable since we can directly access questions associated with the original questionnaire. I used string interpolation, instead of concatenation, for setting the name of the copied questionnaire. I wrapped the copying process in a transaction to ensure data consistency. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
We decided to use before each to setup the questionnaire that we can use for the duration of this describe. For these tests I did use exclusively the test skeleton provided to us and the scenarios felt like complete tests. Testing both a single and multiple valid checkbox questions assert to true. We also want to validate that single or multiple non checkbox questions assert to false.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:True-false-rspec.png]]&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
  describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
    context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_single_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        single_assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: single_assignment.id, questionnaire_id: questionnaire_single_assignment.id})&lt;br /&gt;
        questionnaire_multi_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        multi_assignment1 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment1.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        multi_assignment2 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment2.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_single_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(/^The assignment .* uses this questionnaire/)&lt;br /&gt;
        end&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        assignment1_pattern = /^The assignment #{Regexp.escape(multi_assignment1.name)} uses this questionnaire/&lt;br /&gt;
        assignment2_pattern = /^The assignment #{Regexp.escape(multi_assignment2.name)} uses this questionnaire/&lt;br /&gt;
        # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment1_pattern)&lt;br /&gt;
        end&lt;br /&gt;
        multi_assignment1.destroy!&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment2_pattern)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_one_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_one_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_one_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_multi_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_multi_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        question2 = questionnaire_multi_question.questions.build(weight: 1, id: 2, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question2.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_multi_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        expect(Question.find_by(id: question2.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
      it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: The questionnaire node is deleted&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        q_node = QuestionnaireNode.create(parent_id: 0, node_object_id: questionnaire.id, type: 'QuestionnaireNode')&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(QuestionnaireNode.find_by(id: q_node.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: No error is raised and the method completes successfully&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect { questionnaire.delete }.not_to raise_error&lt;br /&gt;
        expect(Questionnaire.find_by(id: questionnaire.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
 describe '.copy_questionnaire_details' do&lt;br /&gt;
    # Test ensures calls from the method copy_questionnaire_details&lt;br /&gt;
    it 'allowing calls from copy_questionnaire_details' do&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([Question])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    # Test ensures creation of a copy of given questionnaire&lt;br /&gt;
    # Combined two tests into one to avoid performing the same functionalities again to test another feature.&lt;br /&gt;
    it 'creates a copy of the questionnaire' do&lt;br /&gt;
      instructor.save!&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      question1.save!&lt;br /&gt;
      question2.save!&lt;br /&gt;
&lt;br /&gt;
      # Stub the where method to return the questions associated with the original questionnaire&lt;br /&gt;
      allow(questionnaire).to receive_message_chain(:questions, :each).and_yield(question1).and_yield(question2)&lt;br /&gt;
&lt;br /&gt;
      # Call the copy_questionnaire_details method&lt;br /&gt;
      copied_questionnaire = Questionnaire.copy_questionnaire_details({ id: questionnaire.id })&lt;br /&gt;
&lt;br /&gt;
      # Assertions&lt;br /&gt;
      expect(copied_questionnaire.instructor_id).to eq(questionnaire.instructor_id)&lt;br /&gt;
      expect(copied_questionnaire.name).to eq(&amp;quot;Copy of #{questionnaire.name}&amp;quot;)&lt;br /&gt;
      expect(copied_questionnaire.created_at).to be_within(1.second).of(Time.zone.now)&lt;br /&gt;
&lt;br /&gt;
      # Verify that the copied questionnaire has associated questions&lt;br /&gt;
      expect(copied_questionnaire.questions.count).to eq(2)&lt;br /&gt;
      expect(copied_questionnaire.questions.first.txt).to eq(question1.txt)&lt;br /&gt;
      expect(copied_questionnaire.questions.second.txt).to eq(question2.txt)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:True-false-rspec.png&amp;diff=153072</id>
		<title>File:True-false-rspec.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:True-false-rspec.png&amp;diff=153072"/>
		<updated>2024-03-23T14:19:18Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: screenshot of tests for true false method&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
screenshot of tests for true false method&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153071</id>
		<title>E2422. Reimplement questionnaire.rb</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=E2422._Reimplement_questionnaire.rb&amp;diff=153071"/>
		<updated>2024-03-23T14:16:02Z</updated>

		<summary type="html">&lt;p&gt;Bdevine2: /* Validate(s) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Problem Description ==&lt;br /&gt;
The project includes re-implementation of the questionnaire.rb found in Expertiza to the Reimplementation-back-end . The current implementation includes various functionalities such as validation checks and methods. The goal is to implement these ensuring the code follows SOLID principles and is DRY in nature.&lt;br /&gt;
&lt;br /&gt;
Important points &lt;br /&gt;
#The new model should use rails in built active record validation and eliminate the Validate_questionnaire method.&lt;br /&gt;
#The model should check for any overlapping functionalities from the controller and ensure all business logic is present in the model only. &lt;br /&gt;
#Eliminate methods and fields from this model that are exclusively utilized in test files so that only the required changes to the model are merged into the main branch.&lt;br /&gt;
#Ruby naming conventions should be followed for each method.&lt;br /&gt;
#Tests should be performed using Rspec covering all necessary functionalities. &lt;br /&gt;
#Comments should be present for every new functionality that is added and ensure to avoid all mistakes mentioned in the checklist document.&lt;br /&gt;
#Simplification of code is possible in various functions such as get_weighted_score and compute_weighted_score. For more details check the PR which has changes and ideas used for the implementation by the last team working on it. &lt;br /&gt;
#There is already an existing model in the Reimplementation back end . However this is essentially a copy paste from expertiza. This was done by the team working on the controller last spring in 2023. All changes should be made to the same file. Starting from scratch in your fork will be the quickest way to ensure no mix up from the existing implementation.&lt;br /&gt;
&lt;br /&gt;
== Methods Reimplemented ==&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def validate_questionnaire&lt;br /&gt;
    errors.add(:max_question_score, 'The maximum question score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
    errors.add(:min_question_score, 'The minimum question score must be less than the maximum.') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
    results = Questionnaire.where('id &amp;lt;&amp;gt; ? and name = ? and instructor_id = ?', id, name, instructor_id)&lt;br /&gt;
    errors.add(:name, 'Questionnaire names must be unique.') if results.present?&lt;br /&gt;
  end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  validates :name, presence: true&lt;br /&gt;
  validate :name_is_unique&lt;br /&gt;
  validates :max_question_score, numericality: { only_integer: true, greater_than: 0 }, presence: true&lt;br /&gt;
  validates :min_question_score, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, presence: true&lt;br /&gt;
  validate :min_less_than_max&lt;br /&gt;
&lt;br /&gt;
  def name_is_unique&lt;br /&gt;
    # return if we do not have all the values to check&lt;br /&gt;
    return unless id &amp;amp;&amp;amp; name &amp;amp;&amp;amp; instructor_id&lt;br /&gt;
    # check for existing named questionnaire for this instructor that is not this questionnaire&lt;br /&gt;
    existing = Questionnaire.where('name = ? and instructor_id = ? and id &amp;lt;&amp;gt; ?', name, instructor_id, id)&lt;br /&gt;
    errors.add(:name, 'must be unique') if existing.present?&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def min_less_than_max&lt;br /&gt;
    # do we have values if not then do not attempt to validate this&lt;br /&gt;
    return unless min_question_score &amp;amp;&amp;amp; max_question_score&lt;br /&gt;
    errors.add(:min_question_score, 'must be less than max question score') if min_question_score &amp;gt;= max_question_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I changed the validate_questionnaire from a method that needs to be called for validation into individual validates in the order I expected so they could each have single responsibility.&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature -Yang&lt;br /&gt;
    round = AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id).used_in_round&lt;br /&gt;
    questionnaire_symbol = if round.nil?&lt;br /&gt;
                             symbol&lt;br /&gt;
                           else&lt;br /&gt;
                             (symbol.to_s + round.to_s).to_sym&lt;br /&gt;
                           end&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    if scores[symbol][:scores][:avg].nil?&lt;br /&gt;
      0&lt;br /&gt;
    else&lt;br /&gt;
      scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def get_weighted_score(assignment, scores)&lt;br /&gt;
    compute_weighted_score(questionnaire_symbol(assignment), assignment, scores)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
    aq = assignment_questionnaires.find_by(assignment_id: assignment.id)&lt;br /&gt;
    scores[symbol][:scores][:avg].nil? ? 0 : scores[symbol][:scores][:avg] * aq.questionnaire_weight / 100.0&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def questionnaire_symbol(assignment)&lt;br /&gt;
    # create symbol for &amp;quot;varying rubrics&amp;quot; feature&lt;br /&gt;
    # Credit ChatGPT to help me get from the inline below to the used inline, the yield self allowed me the work I wanted to do&lt;br /&gt;
    # AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round ? &amp;quot;#{symbol}#{round}&amp;quot;.to_sym : symbol&lt;br /&gt;
    AssignmentQuestionnaire.find_by(assignment_id: assignment.id, questionnaire_id: id)&amp;amp;.used_in_round&amp;amp;.yield_self { |round| &amp;quot;#{symbol}#{round}&amp;quot;.to_sym } || symbol&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I decided to change this into two public methods and one private method.  Since the original get_weighted_score got the score and looked up the symbol to use I felt it violated the single use principle.  I created a private method to do the lookup of the symbol to use on its own.  I created an elegant line of code to attempt to find an assignment questionnaire and if not null and used in a round it yields the result to be used in the return value.  If not then it will just use this symbol.  I had this line but without the yield as I was attempting to get it doing what I wanted it to do.  I asked chatGPT and it presented me with the yield statement which I then used. Now each method only does what it should, gets the results, computes the result, or evaluates the symbol. &lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
  def true_false_questions?&lt;br /&gt;
    questions.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This method was doing the job very succinctly already. I made no direct changes to this method and left it as is.&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
'''Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    assignments.each do |assignment|&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire.&lt;br /&gt;
            Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node.destroy if node&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
'''After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def delete&lt;br /&gt;
    # Check to see if we go further? we cannot proceed if there are any assignments&lt;br /&gt;
    assignment = assignments.first&lt;br /&gt;
    if assignment&lt;br /&gt;
      raise &amp;quot;The assignment #{assignment.name} uses this questionnaire. Do you want to &amp;lt;A href='../assignment/delete/#{assignment.id}'&amp;gt;delete&amp;lt;/A&amp;gt; the assignment?&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    questions.each(&amp;amp;:delete)&lt;br /&gt;
    node = QuestionnaireNode.find_by(node_object_id: id)&lt;br /&gt;
    node&amp;amp;.destroy&lt;br /&gt;
    destroy&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I did not have to alter this method very much.  First of all there was no reason to do an each on the list if the first one was going to throw an exception already, so I simplified that.  Also just doing a null check on the dereference to the destroy on questionnaire node was more succinct than doing an if.&lt;br /&gt;
&lt;br /&gt;
=== max_possible_score ===&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    results = Questionnaire.joins('INNER JOIN questions ON questions.questionnaire_id = questionnaires.id')&lt;br /&gt;
                           .select('SUM(questions.weight) * questionnaires.max_question_score as max_score')&lt;br /&gt;
                           .where('questionnaires.id = ?', id)&lt;br /&gt;
    results[0].max_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def max_possible_score&lt;br /&gt;
    ## Just return 0 if there are no questions; don't throw an error.&lt;br /&gt;
    return 0 if questions.empty?&lt;br /&gt;
    ## Sums up the weight of all the questions. This is not necessarily 1.&lt;br /&gt;
    sum_of_weights = questions.sum{ | question| quesitons.weight}&lt;br /&gt;
    max_possible_score = sum_of_weights * max_possible_score&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
This refactoring is similar to [https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2023_-_E2319._Reimplement_questionnaire.rb  E2319. Reimplement questionnaire.rb]. Instead of using the complicated SQL query, this should be easier to understand and is rather straightforward. Additionally, we added comments to make it very clear what the logic was doing. Adding the error check at the beginning should make the code slightly more robust in the case of questionnaires with no questions, and the variable names have been changed for clarity.&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details ===&lt;br /&gt;
----&lt;br /&gt;
''' Before refactoring'''&lt;br /&gt;
&lt;br /&gt;
  def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
    questions = Question.where(questionnaire_id: params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
    questionnaire.save!&lt;br /&gt;
    questions.each do |question|&lt;br /&gt;
      new_question = question.dup&lt;br /&gt;
      new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
      new_question.save!&lt;br /&gt;
    end&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
''' After refactoring '''&lt;br /&gt;
&lt;br /&gt;
 def self.copy_questionnaire_details(params)&lt;br /&gt;
    orig_questionnaire = find(params[:id])&lt;br /&gt;
    questionnaire = orig_questionnaire.dup&lt;br /&gt;
    questionnaire.name = &amp;quot;Copy of #{orig_questionnaire.name}&amp;quot;&lt;br /&gt;
    questionnaire.created_at = Time.zone.now&lt;br /&gt;
    questionnaire.updated_at = Time.zone.now&lt;br /&gt;
&lt;br /&gt;
    ActiveRecord::Base.transaction do&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      orig_questionnaire.questions.each do |question|&lt;br /&gt;
        new_question = question.dup&lt;br /&gt;
        new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
        new_question.save!&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    questionnaire&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
I have removed the unnecessary assignment of questions variable since we can directly access questions associated with the original questionnaire. I used string interpolation, instead of concatenation, for setting the name of the copied questionnaire. I wrapped the copying process in a transaction to ensure data consistency. Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.&lt;br /&gt;
&lt;br /&gt;
== Required migrations ==&lt;br /&gt;
The following migrations were required to bring the current reimplementation fork up to par with what was needed to match the Expertiza class.&lt;br /&gt;
&lt;br /&gt;
class AddUsedInRoundToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'used_in_round', :integer&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'used_in_round'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class AddQuestionnarieWeightToAssignmentQuestionnaire &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    add_column 'assignment_questionnaires', 'questionnaire_weight', :integer, default: 0, null: false&lt;br /&gt;
  end&lt;br /&gt;
  def self.down&lt;br /&gt;
    remove_column 'assignment_questionnaires', 'questionnaire_weight'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
class CreateNodes &amp;lt; ActiveRecord::Migration[7.0]&lt;br /&gt;
  def self.up&lt;br /&gt;
    create_table :nodes do |t|&lt;br /&gt;
      t.column :parent_id, :integer&lt;br /&gt;
      t.column :node_object_id, :integer&lt;br /&gt;
      t.column :type, :string&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  def self.down&lt;br /&gt;
    drop_table :nodes&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Rspec Testing ==&lt;br /&gt;
'''Suggested test skeleton'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   # Here are the skeleton rspec tests to be implemented as well, or to replace existing duplicate tests&lt;br /&gt;
&lt;br /&gt;
   describe &amp;quot;#get_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the assignment has a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol with the round appended&amp;quot; do&lt;br /&gt;
         # Test case 1&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
         # Test case 2&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol with the round appended&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the assignment does not have a round&amp;quot; do&lt;br /&gt;
       it &amp;quot;computes the weighted score using the questionnaire symbol&amp;quot; do&lt;br /&gt;
         # Test case 3&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
         # Test case 4&lt;br /&gt;
         # Given an assignment with an ID and a questionnaire with a symbol&lt;br /&gt;
         # And the questionnaire is not used in a round&lt;br /&gt;
         # And a different set of scores&lt;br /&gt;
         # When the get_weighted_score method is called with the assignment and scores&lt;br /&gt;
         # Then it should compute the weighted score using the questionnaire symbol&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
       it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
         # Test scenario&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
         # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
         # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
         # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
     context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
       it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire node is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: No error is raised and the method completes successfully&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
         # Test scenario 1&lt;br /&gt;
         # Given: There are no assignments using the questionnaire&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
         # Test scenario 2&lt;br /&gt;
         # Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
         # When: The delete method is called&lt;br /&gt;
         # Then: The questionnaire is deleted&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#max_possible_score&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the questionnaire has no questions&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns 0 as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with different weights&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weights and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire has questions with the same weight&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns the correct maximum possible score based on the weight and max_question_score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the questionnaire ID does not exist&amp;quot; do&lt;br /&gt;
       it &amp;quot;returns nil as the maximum possible score&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe '.copy_questionnaire_details' do&lt;br /&gt;
     context 'when given valid parameters' do&lt;br /&gt;
       it 'creates a copy of the questionnaire with the instructor_id' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the name of the copied questionnaire as &amp;quot;Copy of [original name]&amp;quot;' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the created_at timestamp of the copied questionnaire to the current time' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the questions from the original questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the questionnaire_id of the copied questions to the id of the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the size of the copied criterion and text response questions to &amp;quot;50,3&amp;quot; if size is nil' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'creates copies of all the question advices associated with the original questions' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'sets the question_id of the copied question advices to the id of the copied question' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'saves the copied question advices' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
&lt;br /&gt;
       it 'returns the copied questionnaire' do&lt;br /&gt;
         # Test body&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   describe &amp;quot;#validate_questionnaire&amp;quot; do&lt;br /&gt;
     context &amp;quot;when the maximum question score is less than 1&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is less than 0&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when the minimum question score is greater than or equal to the maximum question score&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
&lt;br /&gt;
     context &amp;quot;when a questionnaire with the same name and instructor already exists&amp;quot; do&lt;br /&gt;
       it &amp;quot;should add an error message&amp;quot; do&lt;br /&gt;
         # test code&lt;br /&gt;
       end&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Validate(s) ===&lt;br /&gt;
----&lt;br /&gt;
The following list of tests are to assert all the validations for the class. Using the predefined factory variables we test validations.&lt;br /&gt;
Are all names what we expect. Validate we cannot have a valid questionnaire without a name. Create two questionnaires with the same name and instructor to ensure we cannot save the second one. Also validate that the questionnaire returns the assigned instructors id.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 1.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are a few tests to run which can help us test the maximum_score method. We can make sure it matches what is expected, we can assert that alpha characters and floats are not valid. We can follow that up with tests to ensure it is a positive integer, and that the max is larger than the minimum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 2.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The final validations are minimum score and associations. The few tests for minimum score are to check that it is what it should be, that it is positive, and cannot be an alpha character.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Validation 3.png]]&amp;lt;/br&amp;gt;&amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== get_weighted_score ===&lt;br /&gt;
----&lt;br /&gt;
  describe '#get_weighted_score' do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
      @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment has a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol with the round appended' do&lt;br /&gt;
        # Test case 1&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, used_in_round: 1 })&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 2&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { &amp;quot;#{@questionnaire.symbol}#{1}&amp;quot;.to_sym =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context 'when the assignment does not have a round' do&lt;br /&gt;
      it 'computes the weighted score using the questionnaire symbol' do&lt;br /&gt;
        # Test case 3&lt;br /&gt;
        # Arrange&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id })&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 100 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(100)&lt;br /&gt;
        # Test case 4&lt;br /&gt;
        # Arrange&lt;br /&gt;
        scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.get_weighted_score(@assignment, scores)).to eq(75)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    describe &amp;quot;#compute_weighted_score&amp;quot; do&lt;br /&gt;
      before :each do&lt;br /&gt;
        @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        @assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: @assignment.id, questionnaire_id: @questionnaire.id, questionnaire_weight: 50 })&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;returns 0&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: nil } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(0)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
      context &amp;quot;when the average score is not nil&amp;quot; do&lt;br /&gt;
        it &amp;quot;calculates the weighted score based on the questionnaire weight&amp;quot; do&lt;br /&gt;
          # Test scenario&lt;br /&gt;
          # Arrange&lt;br /&gt;
          scores = { @questionnaire.symbol =&amp;gt; { scores: { avg: 75 } } }&lt;br /&gt;
          # Act Assert&lt;br /&gt;
          expect(@questionnaire.compute_weighted_score(@questionnaire.symbol, @assignment, scores)).to eq(75 * 0.5)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== true_false_questions? ===&lt;br /&gt;
----&lt;br /&gt;
  describe &amp;quot;#true_false_questions?&amp;quot; do&lt;br /&gt;
    before :each do&lt;br /&gt;
      @questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when there are true/false questions&amp;quot; do&lt;br /&gt;
      it &amp;quot;returns true&amp;quot; do&lt;br /&gt;
        # Test scenario 2: Single question with type 'Checkbox'&lt;br /&gt;
        # Arrange&lt;br /&gt;
        @questionnaire.questions.create(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Checkbox&amp;quot;, break_before: true)&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.true_false_questions?).to eq(true)&lt;br /&gt;
        # Test scenario 1: Multiple questions with type 'Checkbox'&lt;br /&gt;
        # Arrange&lt;br /&gt;
        @questionnaire.questions.create(weight: 1, id: 2, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Checkbox&amp;quot;, break_before: true)&lt;br /&gt;
        @questionnaire.questions.create(weight: 1, id: 3, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Checkbox&amp;quot;, break_before: true)&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.true_false_questions?).to eq(true)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when there are no true/false questions&amp;quot; do&lt;br /&gt;
      it &amp;quot;returns false&amp;quot; do&lt;br /&gt;
        # Test scenario 2: Single question with no 'Checkbox' type&lt;br /&gt;
        # Arrange&lt;br /&gt;
        @questionnaire.questions.create(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.true_false_questions?).to eq(false)&lt;br /&gt;
        # Test scenario 1: Multiple questions with no 'Checkbox' type&lt;br /&gt;
        # Arrange&lt;br /&gt;
        @questionnaire.questions.create(weight: 1, id: 2, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        @questionnaire.questions.create(weight: 1, id: 3, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect(@questionnaire.true_false_questions?).to eq(false)&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
=== delete === &lt;br /&gt;
----&lt;br /&gt;
  describe &amp;quot;#delete&amp;quot; do&lt;br /&gt;
    context &amp;quot;when there are assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;raises an error with a message asking if the user wants to delete the assignment&amp;quot; do&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_single_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        single_assignment = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: single_assignment.id, questionnaire_id: questionnaire_single_assignment.id})&lt;br /&gt;
        questionnaire_multi_assignment = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        multi_assignment1 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment1.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        multi_assignment2 = FactoryBot.create(:assignment)&lt;br /&gt;
        FactoryBot.create(:assignment_questionnaire, { assignment_id: multi_assignment2.id, questionnaire_id: questionnaire_multi_assignment.id})&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_single_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(/^The assignment .* uses this questionnaire/)&lt;br /&gt;
        end&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        assignment1_pattern = /^The assignment #{Regexp.escape(multi_assignment1.name)} uses this questionnaire/&lt;br /&gt;
        assignment2_pattern = /^The assignment #{Regexp.escape(multi_assignment2.name)} uses this questionnaire/&lt;br /&gt;
        # Given: There are multiple assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: An error is raised for each assignment with a message asking if the user wants to delete the assignment&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment1_pattern)&lt;br /&gt;
        end&lt;br /&gt;
        multi_assignment1.destroy!&lt;br /&gt;
        expect { questionnaire_multi_assignment.delete }.to raise_exception(RuntimeError) do |error|&lt;br /&gt;
          expect(error.message).to match(assignment2_pattern)&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
    context &amp;quot;when there are no assignments using the questionnaire&amp;quot; do&lt;br /&gt;
      it &amp;quot;deletes all the questions associated with the questionnaire&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_one_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_one_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_one_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and there are multiple questions&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: All the questions associated with the questionnaire are deleted&lt;br /&gt;
        # Arrange&lt;br /&gt;
        questionnaire_multi_question = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        question1 = questionnaire_multi_question.questions.build(weight: 1, id: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question1.save!&lt;br /&gt;
        question2 = questionnaire_multi_question.questions.build(weight: 1, id: 2, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;, break_before: true)&lt;br /&gt;
        question2.save!&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire_multi_question.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(Question.find_by(id: question1.id)).to be_nil&lt;br /&gt;
        expect(Question.find_by(id: question2.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
      it &amp;quot;deletes the questionnaire node if it exists&amp;quot; do&lt;br /&gt;
        # Test scenario 1&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node exists&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: The questionnaire node is deleted&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        q_node = QuestionnaireNode.create(parent_id: 0, node_object_id: questionnaire.id, type: 'QuestionnaireNode')&lt;br /&gt;
        # Act&lt;br /&gt;
        questionnaire.delete&lt;br /&gt;
        # Assert&lt;br /&gt;
        expect(QuestionnaireNode.find_by(id: q_node.id)).to be_nil&lt;br /&gt;
        # Test scenario 2&lt;br /&gt;
        # Given: There are no assignments using the questionnaire and the questionnaire node does not exist&lt;br /&gt;
        # When: The delete method is called&lt;br /&gt;
        # Then: No error is raised and the method completes successfully&lt;br /&gt;
        questionnaire = FactoryBot.create(:review_questionnaire, { instructor_id: instructor.id })&lt;br /&gt;
        # Act Assert&lt;br /&gt;
        expect { questionnaire.delete }.not_to raise_error&lt;br /&gt;
        expect(Questionnaire.find_by(id: questionnaire.id)).to be_nil&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
=== copy_questionnaire_details === &lt;br /&gt;
----&lt;br /&gt;
 describe '.copy_questionnaire_details' do&lt;br /&gt;
    # Test ensures calls from the method copy_questionnaire_details&lt;br /&gt;
    it 'allowing calls from copy_questionnaire_details' do&lt;br /&gt;
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)&lt;br /&gt;
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([Question])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    # Test ensures creation of a copy of given questionnaire&lt;br /&gt;
    # Combined two tests into one to avoid performing the same functionalities again to test another feature.&lt;br /&gt;
    it 'creates a copy of the questionnaire' do&lt;br /&gt;
      instructor.save!&lt;br /&gt;
      questionnaire.save!&lt;br /&gt;
      question1.save!&lt;br /&gt;
      question2.save!&lt;br /&gt;
&lt;br /&gt;
      # Stub the where method to return the questions associated with the original questionnaire&lt;br /&gt;
      allow(questionnaire).to receive_message_chain(:questions, :each).and_yield(question1).and_yield(question2)&lt;br /&gt;
&lt;br /&gt;
      # Call the copy_questionnaire_details method&lt;br /&gt;
      copied_questionnaire = Questionnaire.copy_questionnaire_details({ id: questionnaire.id })&lt;br /&gt;
&lt;br /&gt;
      # Assertions&lt;br /&gt;
      expect(copied_questionnaire.instructor_id).to eq(questionnaire.instructor_id)&lt;br /&gt;
      expect(copied_questionnaire.name).to eq(&amp;quot;Copy of #{questionnaire.name}&amp;quot;)&lt;br /&gt;
      expect(copied_questionnaire.created_at).to be_within(1.second).of(Time.zone.now)&lt;br /&gt;
&lt;br /&gt;
      # Verify that the copied questionnaire has associated questions&lt;br /&gt;
      expect(copied_questionnaire.questions.count).to eq(2)&lt;br /&gt;
      expect(copied_questionnaire.questions.first.txt).to eq(question1.txt)&lt;br /&gt;
      expect(copied_questionnaire.questions.second.txt).to eq(question2.txt)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
The below tests were redundant&lt;br /&gt;
----&lt;br /&gt;
  it &amp;quot;deletes the questionnaire&amp;quot; do&lt;br /&gt;
        #Test scenario 1&lt;br /&gt;
        #Given: There are no assignments using the questionnaire&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
&lt;br /&gt;
        #Test scenario 2&lt;br /&gt;
        #Given: There are no assignments using the questionnaire and there are multiple questionnaires&lt;br /&gt;
        #When: The delete method is called&lt;br /&gt;
        #Then: The questionnaire is deleted&lt;br /&gt;
  end&lt;/div&gt;</summary>
		<author><name>Bdevine2</name></author>
	</entry>
</feed>