<?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=Djhanse2</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=Djhanse2"/>
	<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=Special:Contributions/Djhanse2"/>
	<updated>2026-05-24T22:05:25Z</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_2026_-_E2617._Testing_Questionnaire_and_Course_Models&amp;diff=167945</id>
		<title>CSC/ECE 517 Spring 2026 - E2617. Testing Questionnaire and Course Models</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2617._Testing_Questionnaire_and_Course_Models&amp;diff=167945"/>
		<updated>2026-04-14T02:15:40Z</updated>

		<summary type="html">&lt;p&gt;Djhanse2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
== Project Description ==&lt;br /&gt;
&lt;br /&gt;
This project is oriented around the testing of three models representing the individual reviews that each student will be conducting for an assignment. The Questionnaire model represents a review rubric used in assignments. It holds a set of scored questions with configurable min/max score bounds, belongs to an instructor, and supports being copied (along with all its questions and advice) for reuse. It also provides scoring utilities used during assignment evaluation. The QuestionAdvice model represents the score-specific feedback hints attached to a question in a questionnaire (e.g., &amp;quot;if you give a score of 3, here's what that means&amp;quot;). It provides class-level methods to export advice records to CSV and to fetch advice for a given question as JSON. The Course model represents a course in the system. It manages the course's filesystem submission path, handles adding and removing Teaching Assistants (updating their role accordingly via TaMapping), and supports duplicating a course via copy_course. The emphasis is on making sure that the methods associated with the model classes for Questionnaires, QuestionAdvice, and Course are tested using RSpec unit testing and resolving any bugs that are present in the current implementation.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;; margin-left:20px; width:320px;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; style=&amp;quot;background:#cee0f2; text-align:center;&amp;quot; | Project Info&lt;br /&gt;
|-&lt;br /&gt;
| '''Course''' || CSC/ECE 517 Spring 2026&lt;br /&gt;
|-&lt;br /&gt;
| '''Project''' || E2617 — Testing Questionnaire and Course Models&lt;br /&gt;
|-&lt;br /&gt;
| '''Instructor''' || Ed Gehringer&lt;br /&gt;
|-&lt;br /&gt;
| '''Mentor''' || Aanand Sreekumaran Nair Jayakumari&lt;br /&gt;
|-&lt;br /&gt;
| '''Collaborators''' || DJ Hansen, Zack Brooks, Matt Nguyen&lt;br /&gt;
|-&lt;br /&gt;
| '''Platform''' || Expertiza (Ruby on Rails)&lt;br /&gt;
|-&lt;br /&gt;
| '''Test Framework''' || RSpec&lt;br /&gt;
|-&lt;br /&gt;
| '''Root Class''' || &amp;lt;code&amp;gt;ApplicationRecord&amp;lt;/code&amp;gt; (STI superclass)&lt;br /&gt;
|-&lt;br /&gt;
| '''Subclasses''' || &amp;lt;code&amp;gt;Questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuestionAdvice&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Course&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire Model ===&lt;br /&gt;
&lt;br /&gt;
==== Questionnaire Attributes ====&lt;br /&gt;
* id: '''integer'''&lt;br /&gt;
* name: '''string'''&lt;br /&gt;
* instructor_id: '''integer'''&lt;br /&gt;
* private: '''boolean'''&lt;br /&gt;
* min_question_score: '''integer'''&lt;br /&gt;
* max_question_score: '''integer'''&lt;br /&gt;
* questionnaire_type: '''string'''&lt;br /&gt;
* display_type: '''string'''&lt;br /&gt;
* instruction_loc: '''text'''&lt;br /&gt;
* created_at: '''datetime'''&lt;br /&gt;
* updated_at: '''datetime'''&lt;br /&gt;
&lt;br /&gt;
==== Key Relationships ====&lt;br /&gt;
&lt;br /&gt;
* Stores all questions belonging to this questionnaire. Deleting the questionnaire removes its items.  &lt;br /&gt;
  '''has_many :items, foreign_key: &amp;quot;questionnaire_id&amp;quot;, dependent: :destroy'''&lt;br /&gt;
&lt;br /&gt;
* Identifies the instructor who owns this questionnaire.  &lt;br /&gt;
  '''belongs_to :instructor&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== QuestionAdvice Model ===&lt;br /&gt;
&lt;br /&gt;
==== QuestionAdvice Attributes ====&lt;br /&gt;
* id: '''integer'''&lt;br /&gt;
* question_id: '''integer'''&lt;br /&gt;
* score: '''integer'''&lt;br /&gt;
* advice: '''text'''&lt;br /&gt;
* created_at: '''datetime'''&lt;br /&gt;
* updated_at: '''datetime'''&lt;br /&gt;
&lt;br /&gt;
==== Key Relationships ====&lt;br /&gt;
&lt;br /&gt;
* Links advice to a specific question (item).  &lt;br /&gt;
  '''belongs_to :item'''&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Course Model ===&lt;br /&gt;
&lt;br /&gt;
==== Course Attributes ====&lt;br /&gt;
* id: '''integer'''&lt;br /&gt;
* name: '''string'''&lt;br /&gt;
* directory_path: '''string'''&lt;br /&gt;
* info: '''text'''&lt;br /&gt;
* private: '''boolean'''&lt;br /&gt;
* created_at: '''datetime'''&lt;br /&gt;
* updated_at: '''datetime'''&lt;br /&gt;
* instructor_id: '''integer'''&lt;br /&gt;
* institution_id: '''integer'''&lt;br /&gt;
&lt;br /&gt;
==== Key Relationships ====&lt;br /&gt;
&lt;br /&gt;
* Identifies the instructor responsible for the course.  &lt;br /&gt;
  '''belongs_to :instructor, class_name: 'User', foreign_key: 'instructor_id'''&lt;br /&gt;
&lt;br /&gt;
* Associates the course with an institution.  &lt;br /&gt;
  '''belongs_to :institution, foreign_key: 'institution_id'''&lt;br /&gt;
&lt;br /&gt;
* Stores all assignments in the course; removed when the course is deleted.  &lt;br /&gt;
  '''has_many :assignments, dependent: :destroy'''&lt;br /&gt;
&lt;br /&gt;
* Manages enrolled users through a join table.  &lt;br /&gt;
  '''has_many :participants, class_name: 'CourseParticipant', foreign_key: 'parent_id', dependent: :destroy, inverse_of: :course'''  &lt;br /&gt;
  '''has_many :users, through: :course_participants, inverse_of: :course'''&lt;br /&gt;
&lt;br /&gt;
* Manages teaching assistants for the course.  &lt;br /&gt;
  '''has_many :ta_mappings, dependent: :destroy'''  &lt;br /&gt;
  '''has_many :tas, through: :ta_mappings, source: :ta'''&lt;br /&gt;
&lt;br /&gt;
* Stores teams within the course.  &lt;br /&gt;
  '''has_many :teams, class_name: 'CourseTeam', foreign_key: 'parent_id', dependent: :destroy, inverse_of: :course'''&lt;br /&gt;
&lt;br /&gt;
== Method Description ==&lt;br /&gt;
The following section discusses the current implementation of methods that our group will be unit testing.&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire Model Methods ===&lt;br /&gt;
&lt;br /&gt;
==== 1. validate_questionnaire ====&lt;br /&gt;
Validates a questionnaire by ensuring:&lt;br /&gt;
* Maximum score is a positive integer&lt;br /&gt;
* Minimum score is a non-negative integer&lt;br /&gt;
* Minimum score is less than the maximum score&lt;br /&gt;
* Questionnaire name is unique per instructor&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def validate_questionnaire&lt;br /&gt;
  errors.add(:max_question_score, 'The maximum item score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
  errors.add(:min_question_score, 'The minimum item score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
  errors.add(:min_question_score, 'The minimum item 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;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. self.copy_questionnaire_details ====&lt;br /&gt;
Clones a questionnaire, including its items and associated advice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def self.copy_questionnaire_details(params)&lt;br /&gt;
  orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  items = Item.where(questionnaire_id: params[:id])&lt;br /&gt;
  questionnaire = orig_questionnaire.dup&lt;br /&gt;
  questionnaire.instructor_id = params[:instructor_id]&lt;br /&gt;
  questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
  questionnaire.created_at = Time.zone.now&lt;br /&gt;
  questionnaire.save!&lt;br /&gt;
&lt;br /&gt;
  items.each do |question|&lt;br /&gt;
    new_question = question.dup&lt;br /&gt;
    new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
    new_question.size = '50,3' if (new_question.is_a?(Criterion) || new_question.is_a?(TextResponse)) &amp;amp;&amp;amp; new_question.size.nil?&lt;br /&gt;
    new_question.save!&lt;br /&gt;
&lt;br /&gt;
    advice = QuestionAdvice.where(question_id: question.id)&lt;br /&gt;
    next if advice.empty?&lt;br /&gt;
&lt;br /&gt;
    advice.each do |advice|&lt;br /&gt;
      new_advice = advice.dup&lt;br /&gt;
      new_advice.question_id = new_question.id&lt;br /&gt;
      new_advice.save!&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  questionnaire&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. check_for_question_associations ====&lt;br /&gt;
Checks whether the questionnaire has associated items before deletion.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def check_for_question_associations&lt;br /&gt;
  if items.any?&lt;br /&gt;
    raise ActiveRecord::DeleteRestrictionError.new(&amp;quot;Cannot delete record because dependent items exist&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. get_weighted_score ====&lt;br /&gt;
Calculates the weighted score by generating a symbol and calling a helper method.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
&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;
&lt;br /&gt;
  compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. compute_weighted_score ====&lt;br /&gt;
Helper method that computes the weighted score for an assignment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
  aq = AssignmentQuestionnaire.find_by(assignment_id: assignment.id)&lt;br /&gt;
&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;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. true_false_items? ====&lt;br /&gt;
Checks if the questionnaire contains any true/false (checkbox) items.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def true_false_items?&lt;br /&gt;
  items.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
  false&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. max_possible_score ====&lt;br /&gt;
Calculates the maximum possible score based on item weights and max question score.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def max_possible_score&lt;br /&gt;
  results = Questionnaire.joins('INNER JOIN items ON items.questionnaire_id = questionnaires.id')&lt;br /&gt;
                         .select('SUM(items.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;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== QuestionAdvice Model Methods ===&lt;br /&gt;
&lt;br /&gt;
==== 1. self.export_fields ====&lt;br /&gt;
Returns all column names of QuestionAdvice as strings.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def self.export_fields(_options)&lt;br /&gt;
  QuestionAdvice.columns.map(&amp;amp;:name)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. self.export ====&lt;br /&gt;
Exports QuestionAdvice entries to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def self.export(csv, parent_id, _options)&lt;br /&gt;
  questionnaire = Questionnaire.find(parent_id)&lt;br /&gt;
&lt;br /&gt;
  questionnaire.items.each do |item|&lt;br /&gt;
    QuestionAdvice.where(question_id: item.id).each do |advice|&lt;br /&gt;
      csv &amp;lt;&amp;lt; advice.attributes.values&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;
==== 3. self.to_json_by_question_id ====&lt;br /&gt;
Exports QuestionAdvice entries to JSON format by question ID.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def self.to_json_by_question_id(question_id)&lt;br /&gt;
  question_advices = QuestionAdvice.where(question_id: question_id).order(:id)&lt;br /&gt;
&lt;br /&gt;
  question_advices.map do |advice|&lt;br /&gt;
    { score: advice.score, advice: advice.advice }&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Course Model Methods ===&lt;br /&gt;
&lt;br /&gt;
==== 1. path ====&lt;br /&gt;
Returns the submission directory path for the course.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def path&lt;br /&gt;
  raise 'Path can not be created as the course must be associated with an instructor.' if instructor_id.nil?&lt;br /&gt;
&lt;br /&gt;
  Rails.root + '/' +&lt;br /&gt;
    Institution.find(institution_id).name.gsub(&amp;quot; &amp;quot;, &amp;quot;&amp;quot;) + '/' +&lt;br /&gt;
    User.find(instructor_id).name.gsub(&amp;quot; &amp;quot;, &amp;quot;&amp;quot;) + '/' +&lt;br /&gt;
    directory_path + '/'&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. add_ta ====&lt;br /&gt;
Adds a Teaching Assistant (TA) to the course.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def add_ta(user)&lt;br /&gt;
  if user.nil?&lt;br /&gt;
    return { success: false, message: &amp;quot;The user with id #{user.id} does not exist&amp;quot; }&lt;br /&gt;
&lt;br /&gt;
  elsif TaMapping.exists?(user_id: user.id, course_id: id)&lt;br /&gt;
    return { success: false, message: &amp;quot;The user with id #{user.id} is already a TA for this course.&amp;quot; }&lt;br /&gt;
&lt;br /&gt;
  else&lt;br /&gt;
    ta_mapping = TaMapping.create(user_id: user.id, course_id: id)&lt;br /&gt;
    ta_role = Role.find_by(name: 'Teaching Assistant')&lt;br /&gt;
    user.update(role: ta_role) if ta_role&lt;br /&gt;
&lt;br /&gt;
    if ta_mapping.save&lt;br /&gt;
      return { success: true, data: ta_mapping.slice(:course_id, :user_id) }&lt;br /&gt;
    else&lt;br /&gt;
      return { success: false, message: ta_mapping.errors }&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;
==== 3. remove_ta ====&lt;br /&gt;
Removes a Teaching Assistant from the course.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def remove_ta(user_id)&lt;br /&gt;
  ta_mapping = ta_mappings.find_by(user_id: user_id, course_id: :id)&lt;br /&gt;
  return { success: false, message: &amp;quot;No TA mapping found for the specified course and TA&amp;quot; } if ta_mapping.nil?&lt;br /&gt;
&lt;br /&gt;
  ta = User.find(ta_mapping.user_id)&lt;br /&gt;
  ta_count = TaMapping.where(user_id: user_id).size - 1&lt;br /&gt;
&lt;br /&gt;
  if ta_count.zero?&lt;br /&gt;
    ta.update(role: Role::STUDENT)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  ta_mapping.destroy&lt;br /&gt;
  { success: true, ta_name: ta.name }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. copy_course ====&lt;br /&gt;
Creates a duplicate of the course.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def copy_course&lt;br /&gt;
  new_course = dup&lt;br /&gt;
  new_course.directory_path += '_copy'&lt;br /&gt;
  new_course.name += '_copy'&lt;br /&gt;
  new_course.save&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Issues in Existing Code==&lt;br /&gt;
&lt;br /&gt;
=== Bugs ===&lt;br /&gt;
The following statements in the Questionnaire model have inconsistent naming as the first statement relies on the relationship &amp;quot;participants&amp;quot; while the second statement relies on the relationship &amp;quot;course_participants&amp;quot; which doesn't exist.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
has_many :participants, class_name: 'CourseParticipant', foreign_key: 'parent_id', dependent: :destroy, inverse_of: :course&lt;br /&gt;
has_many :users, through: :course_participants, inverse_of: :course&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Coverage Issues ===&lt;br /&gt;
&lt;br /&gt;
spec/models/questionnaire_spec.rb exists but only covers validations, associations, and copy_questionnaire_details. Several instance methods and the entire QuestionAdvice model have less coverage.&lt;br /&gt;
&lt;br /&gt;
spec/models/course_spec.rb only tests validations and the path method. Three instance methods - add_ta, remove_ta, and copy_course - have zero unit test coverage.&lt;br /&gt;
&lt;br /&gt;
== Test Plan ==&lt;br /&gt;
&lt;br /&gt;
For this project, we will be using factories and fixtures to generate our models for unit testing with RSpec.&lt;br /&gt;
&lt;br /&gt;
==== Questionnaire ====&lt;br /&gt;
&lt;br /&gt;
Currently for Questionnaire, the data for unit testing is currently being created using Factory so we will continue to write tests that ensure over 80% code coverage. We will also use factories for the Course model as the method logic is dependent on varying data. It allows us to adjust parameters and test for edge cases easier.&lt;br /&gt;
&lt;br /&gt;
The following code is currently how the factory for the Questionnaire model is set up.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# spec/factories/questionnaires.rb&lt;br /&gt;
FactoryBot.define do&lt;br /&gt;
  factory :questionnaire do&lt;br /&gt;
    sequence(:name) { |n| &amp;quot;Questionnaire #{n}&amp;quot; }&lt;br /&gt;
    private { false }&lt;br /&gt;
    min_question_score { 0 }&lt;br /&gt;
    max_question_score { 10 }&lt;br /&gt;
    association :instructor&lt;br /&gt;
    association :assignment&lt;br /&gt;
&lt;br /&gt;
    # Trait for questionnaire with questions&lt;br /&gt;
    trait :with_questions do&lt;br /&gt;
      after(:create) do |questionnaire|&lt;br /&gt;
        create(:item, questionnaire: questionnaire, weight: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;)&lt;br /&gt;
        create(:item, questionnaire: questionnaire, weight: 10, seq: 2, txt: &amp;quot;que 2&amp;quot;, question_type: &amp;quot;Checkbox&amp;quot;)&lt;br /&gt;
      end&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;
The following code serves as the factory for generating instances of items which are essentially questions in a questionnaire object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# spec/factories/items.rb&lt;br /&gt;
FactoryBot.define do&lt;br /&gt;
  factory :item do&lt;br /&gt;
    sequence(:txt) { |n| &amp;quot;Question #{n}&amp;quot; }&lt;br /&gt;
    sequence(:seq) { |n| n }&lt;br /&gt;
    weight { 1 }&lt;br /&gt;
    question_type { &amp;quot;Scale&amp;quot; }&lt;br /&gt;
    break_before { true }&lt;br /&gt;
    association :questionnaire&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Course ====&lt;br /&gt;
&lt;br /&gt;
The following code is the current version of the factory for the course models.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FactoryBot.define do&lt;br /&gt;
  factory :course do&lt;br /&gt;
    name { Faker::Educator.course_name }&lt;br /&gt;
    info { Faker::Lorem.paragraph }&lt;br /&gt;
    private { false }&lt;br /&gt;
    directory_path { Faker::File.dir }&lt;br /&gt;
    association :institution, factory: :institution&lt;br /&gt;
    association :instructor, factory: :user&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== QuestionAdvice ====&lt;br /&gt;
&lt;br /&gt;
For QuestionAdvice use fixtures to load data into database to test that it is being saved and exported properly. As it stands, there isn't that much logical complexity with the data inside QuestionAdvice itself as it is more concerned with exporting data into csv or json files for storage. The fixtures are stored in question_advice.yml under the fixtures folder.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#question_advice.yml&lt;br /&gt;
&lt;br /&gt;
question_advice_1:  &lt;br /&gt;
  id: 1&lt;br /&gt;
  question_id: 1&lt;br /&gt;
  score: 5&lt;br /&gt;
  advice: &amp;quot;This is some advice for question 1.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
question_advice_2:&lt;br /&gt;
  id: 2&lt;br /&gt;
  question_id: 2&lt;br /&gt;
  score: 3&lt;br /&gt;
  advice: &amp;quot;This is some advice for question 2.&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Coverage and Current Results ==&lt;br /&gt;
SimpleCov report for models under test can be found in the [https://darrinj22.github.io/#_AllFiles Coverage Report].&lt;br /&gt;
&lt;br /&gt;
Test Results:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! % covered !! Lines !! Relevant Lines !! Lines covered !! Lines missed !! Avg. Hits / Line&lt;br /&gt;
|-&lt;br /&gt;
| app/models/questionnaire.rb || 64.06% || 141 || 64 || 41 || 23 || 3.02&lt;br /&gt;
|-&lt;br /&gt;
| app/models/question_advice.rb || 38.46% || 24 || 13 || 5 || 8 || 0.38&lt;br /&gt;
|-&lt;br /&gt;
| app/models/course.rb || 43.59% || 59 || 39 || 17 || 22 || 0.46&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
This section will be updated with final results at the end of the project.&lt;br /&gt;
&lt;br /&gt;
== Github == &lt;br /&gt;
Link to Github Project repo: [https://github.com/MATTMINWIN/reimplementation-back-end]&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Djhanse2</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2617._Testing_Questionnaire_and_Course_Models&amp;diff=167942</id>
		<title>CSC/ECE 517 Spring 2026 - E2617. Testing Questionnaire and Course Models</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2617._Testing_Questionnaire_and_Course_Models&amp;diff=167942"/>
		<updated>2026-04-14T02:01:16Z</updated>

		<summary type="html">&lt;p&gt;Djhanse2: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
== Project Description ==&lt;br /&gt;
&lt;br /&gt;
This project is oriented around the testing of three models representing the individual reviews that each student will be conducting for an assignment. The Questionnaire model represents a review rubric used in assignments. It holds a set of scored questions with configurable min/max score bounds, belongs to an instructor, and supports being copied (along with all its questions and advice) for reuse. It also provides scoring utilities used during assignment evaluation. The QuestionAdvice model represents the score-specific feedback hints attached to a question in a questionnaire (e.g., &amp;quot;if you give a score of 3, here's what that means&amp;quot;). It provides class-level methods to export advice records to CSV and to fetch advice for a given question as JSON. The Course model represents a course in the system. It manages the course's filesystem submission path, handles adding and removing Teaching Assistants (updating their role accordingly via TaMapping), and supports duplicating a course via copy_course. The emphasis is on making sure that the methods associated with the model classes for Questionnaires, QuestionAdvice, and Course are tested using RSpec unit testing and resolving any bugs that are present in the current implementation.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;; margin-left:20px; width:320px;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; style=&amp;quot;background:#cee0f2; text-align:center;&amp;quot; | Project Info&lt;br /&gt;
|-&lt;br /&gt;
| '''Course''' || CSC/ECE 517 Spring 2026&lt;br /&gt;
|-&lt;br /&gt;
| '''Project''' || E2617 — Testing Questionnaire and Course Models&lt;br /&gt;
|-&lt;br /&gt;
| '''Instructor''' || Ed Gehringer&lt;br /&gt;
|-&lt;br /&gt;
| '''Mentor''' || Aanand Sreekumaran Nair Jayakumari&lt;br /&gt;
|-&lt;br /&gt;
| '''Collaborators''' || DJ Hansen, Zack Brooks, Matt Nguyen&lt;br /&gt;
|-&lt;br /&gt;
| '''Platform''' || Expertiza (Ruby on Rails)&lt;br /&gt;
|-&lt;br /&gt;
| '''Test Framework''' || RSpec&lt;br /&gt;
|-&lt;br /&gt;
| '''Root Class''' || &amp;lt;code&amp;gt;ApplicationRecord&amp;lt;/code&amp;gt; (STI superclass)&lt;br /&gt;
|-&lt;br /&gt;
| '''Subclasses''' || &amp;lt;code&amp;gt;Questionnaire&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;QuestionAdvice&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Course&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire Model ===&lt;br /&gt;
&lt;br /&gt;
==== Questionnaire Attributes ====&lt;br /&gt;
* id: '''integer'''&lt;br /&gt;
* name: '''string'''&lt;br /&gt;
* instructor_id: '''integer'''&lt;br /&gt;
* private: '''boolean'''&lt;br /&gt;
* min_question_score: '''integer'''&lt;br /&gt;
* max_question_score: '''integer'''&lt;br /&gt;
* questionnaire_type: '''string'''&lt;br /&gt;
* display_type: '''string'''&lt;br /&gt;
* instruction_loc: '''text'''&lt;br /&gt;
* created_at: '''datetime'''&lt;br /&gt;
* updated_at: '''datetime'''&lt;br /&gt;
&lt;br /&gt;
==== Key Relationships ====&lt;br /&gt;
&lt;br /&gt;
* Stores all questions belonging to this questionnaire. Deleting the questionnaire removes its items.  &lt;br /&gt;
  '''has_many :items, foreign_key: &amp;quot;questionnaire_id&amp;quot;, dependent: :destroy'''&lt;br /&gt;
&lt;br /&gt;
* Identifies the instructor who owns this questionnaire.  &lt;br /&gt;
  '''belongs_to :instructor&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== QuestionAdvice Model ===&lt;br /&gt;
&lt;br /&gt;
==== QuestionAdvice Attributes ====&lt;br /&gt;
* id: '''integer'''&lt;br /&gt;
* question_id: '''integer'''&lt;br /&gt;
* score: '''integer'''&lt;br /&gt;
* advice: '''text'''&lt;br /&gt;
* created_at: '''datetime'''&lt;br /&gt;
* updated_at: '''datetime'''&lt;br /&gt;
&lt;br /&gt;
==== Key Relationships ====&lt;br /&gt;
&lt;br /&gt;
* Links advice to a specific question (item).  &lt;br /&gt;
  '''belongs_to :item'''&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Course Model ===&lt;br /&gt;
&lt;br /&gt;
==== Course Attributes ====&lt;br /&gt;
* id: '''integer'''&lt;br /&gt;
* name: '''string'''&lt;br /&gt;
* directory_path: '''string'''&lt;br /&gt;
* info: '''text'''&lt;br /&gt;
* private: '''boolean'''&lt;br /&gt;
* created_at: '''datetime'''&lt;br /&gt;
* updated_at: '''datetime'''&lt;br /&gt;
* instructor_id: '''integer'''&lt;br /&gt;
* institution_id: '''integer'''&lt;br /&gt;
&lt;br /&gt;
==== Key Relationships ====&lt;br /&gt;
&lt;br /&gt;
* Identifies the instructor responsible for the course.  &lt;br /&gt;
  '''belongs_to :instructor, class_name: 'User', foreign_key: 'instructor_id'''&lt;br /&gt;
&lt;br /&gt;
* Associates the course with an institution.  &lt;br /&gt;
  '''belongs_to :institution, foreign_key: 'institution_id'''&lt;br /&gt;
&lt;br /&gt;
* Stores all assignments in the course; removed when the course is deleted.  &lt;br /&gt;
  '''has_many :assignments, dependent: :destroy'''&lt;br /&gt;
&lt;br /&gt;
* Manages enrolled users through a join table.  &lt;br /&gt;
  '''has_many :participants, class_name: 'CourseParticipant', foreign_key: 'parent_id', dependent: :destroy, inverse_of: :course'''  &lt;br /&gt;
  '''has_many :users, through: :course_participants, inverse_of: :course'''&lt;br /&gt;
&lt;br /&gt;
* Manages teaching assistants for the course.  &lt;br /&gt;
  '''has_many :ta_mappings, dependent: :destroy'''  &lt;br /&gt;
  '''has_many :tas, through: :ta_mappings, source: :ta'''&lt;br /&gt;
&lt;br /&gt;
* Stores teams within the course.  &lt;br /&gt;
  '''has_many :teams, class_name: 'CourseTeam', foreign_key: 'parent_id', dependent: :destroy, inverse_of: :course'''&lt;br /&gt;
&lt;br /&gt;
== Method Description ==&lt;br /&gt;
The following section discusses the current implementation of methods that our group will be unit testing.&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire Model Methods ===&lt;br /&gt;
&lt;br /&gt;
==== 1. validate_questionnaire ====&lt;br /&gt;
Validates a questionnaire by ensuring:&lt;br /&gt;
* Maximum score is a positive integer&lt;br /&gt;
* Minimum score is a non-negative integer&lt;br /&gt;
* Minimum score is less than the maximum score&lt;br /&gt;
* Questionnaire name is unique per instructor&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def validate_questionnaire&lt;br /&gt;
  errors.add(:max_question_score, 'The maximum item score must be a positive integer.') if max_question_score &amp;lt; 1&lt;br /&gt;
  errors.add(:min_question_score, 'The minimum item score must be a positive integer.') if min_question_score &amp;lt; 0&lt;br /&gt;
  errors.add(:min_question_score, 'The minimum item 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;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. self.copy_questionnaire_details ====&lt;br /&gt;
Clones a questionnaire, including its items and associated advice.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def self.copy_questionnaire_details(params)&lt;br /&gt;
  orig_questionnaire = Questionnaire.find(params[:id])&lt;br /&gt;
  items = Item.where(questionnaire_id: params[:id])&lt;br /&gt;
  questionnaire = orig_questionnaire.dup&lt;br /&gt;
  questionnaire.instructor_id = params[:instructor_id]&lt;br /&gt;
  questionnaire.name = 'Copy of ' + orig_questionnaire.name&lt;br /&gt;
  questionnaire.created_at = Time.zone.now&lt;br /&gt;
  questionnaire.save!&lt;br /&gt;
&lt;br /&gt;
  items.each do |question|&lt;br /&gt;
    new_question = question.dup&lt;br /&gt;
    new_question.questionnaire_id = questionnaire.id&lt;br /&gt;
    new_question.size = '50,3' if (new_question.is_a?(Criterion) || new_question.is_a?(TextResponse)) &amp;amp;&amp;amp; new_question.size.nil?&lt;br /&gt;
    new_question.save!&lt;br /&gt;
&lt;br /&gt;
    advice = QuestionAdvice.where(question_id: question.id)&lt;br /&gt;
    next if advice.empty?&lt;br /&gt;
&lt;br /&gt;
    advice.each do |advice|&lt;br /&gt;
      new_advice = advice.dup&lt;br /&gt;
      new_advice.question_id = new_question.id&lt;br /&gt;
      new_advice.save!&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  questionnaire&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 3. check_for_question_associations ====&lt;br /&gt;
Checks whether the questionnaire has associated items before deletion.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def check_for_question_associations&lt;br /&gt;
  if items.any?&lt;br /&gt;
    raise ActiveRecord::DeleteRestrictionError.new(&amp;quot;Cannot delete record because dependent items exist&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. get_weighted_score ====&lt;br /&gt;
Calculates the weighted score by generating a symbol and calling a helper method.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
&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;
&lt;br /&gt;
  compute_weighted_score(questionnaire_symbol, assignment, scores)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 5. compute_weighted_score ====&lt;br /&gt;
Helper method that computes the weighted score for an assignment.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def compute_weighted_score(symbol, assignment, scores)&lt;br /&gt;
  aq = AssignmentQuestionnaire.find_by(assignment_id: assignment.id)&lt;br /&gt;
&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;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 6. true_false_items? ====&lt;br /&gt;
Checks if the questionnaire contains any true/false (checkbox) items.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def true_false_items?&lt;br /&gt;
  items.each { |question| return true if question.type == 'Checkbox' }&lt;br /&gt;
  false&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 7. max_possible_score ====&lt;br /&gt;
Calculates the maximum possible score based on item weights and max question score.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def max_possible_score&lt;br /&gt;
  results = Questionnaire.joins('INNER JOIN items ON items.questionnaire_id = questionnaires.id')&lt;br /&gt;
                         .select('SUM(items.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;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== QuestionAdvice Model Methods ===&lt;br /&gt;
&lt;br /&gt;
==== 1. self.export_fields ====&lt;br /&gt;
Returns all column names of QuestionAdvice as strings.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def self.export_fields(_options)&lt;br /&gt;
  QuestionAdvice.columns.map(&amp;amp;:name)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. self.export ====&lt;br /&gt;
Exports QuestionAdvice entries to a CSV file.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def self.export(csv, parent_id, _options)&lt;br /&gt;
  questionnaire = Questionnaire.find(parent_id)&lt;br /&gt;
&lt;br /&gt;
  questionnaire.items.each do |item|&lt;br /&gt;
    QuestionAdvice.where(question_id: item.id).each do |advice|&lt;br /&gt;
      csv &amp;lt;&amp;lt; advice.attributes.values&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;
==== 3. self.to_json_by_question_id ====&lt;br /&gt;
Exports QuestionAdvice entries to JSON format by question ID.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def self.to_json_by_question_id(question_id)&lt;br /&gt;
  question_advices = QuestionAdvice.where(question_id: question_id).order(:id)&lt;br /&gt;
&lt;br /&gt;
  question_advices.map do |advice|&lt;br /&gt;
    { score: advice.score, advice: advice.advice }&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Course Model Methods ===&lt;br /&gt;
&lt;br /&gt;
==== 1. path ====&lt;br /&gt;
Returns the submission directory path for the course.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def path&lt;br /&gt;
  raise 'Path can not be created as the course must be associated with an instructor.' if instructor_id.nil?&lt;br /&gt;
&lt;br /&gt;
  Rails.root + '/' +&lt;br /&gt;
    Institution.find(institution_id).name.gsub(&amp;quot; &amp;quot;, &amp;quot;&amp;quot;) + '/' +&lt;br /&gt;
    User.find(instructor_id).name.gsub(&amp;quot; &amp;quot;, &amp;quot;&amp;quot;) + '/' +&lt;br /&gt;
    directory_path + '/'&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 2. add_ta ====&lt;br /&gt;
Adds a Teaching Assistant (TA) to the course.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def add_ta(user)&lt;br /&gt;
  if user.nil?&lt;br /&gt;
    return { success: false, message: &amp;quot;The user with id #{user.id} does not exist&amp;quot; }&lt;br /&gt;
&lt;br /&gt;
  elsif TaMapping.exists?(user_id: user.id, course_id: id)&lt;br /&gt;
    return { success: false, message: &amp;quot;The user with id #{user.id} is already a TA for this course.&amp;quot; }&lt;br /&gt;
&lt;br /&gt;
  else&lt;br /&gt;
    ta_mapping = TaMapping.create(user_id: user.id, course_id: id)&lt;br /&gt;
    ta_role = Role.find_by(name: 'Teaching Assistant')&lt;br /&gt;
    user.update(role: ta_role) if ta_role&lt;br /&gt;
&lt;br /&gt;
    if ta_mapping.save&lt;br /&gt;
      return { success: true, data: ta_mapping.slice(:course_id, :user_id) }&lt;br /&gt;
    else&lt;br /&gt;
      return { success: false, message: ta_mapping.errors }&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;
==== 3. remove_ta ====&lt;br /&gt;
Removes a Teaching Assistant from the course.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def remove_ta(user_id)&lt;br /&gt;
  ta_mapping = ta_mappings.find_by(user_id: user_id, course_id: :id)&lt;br /&gt;
  return { success: false, message: &amp;quot;No TA mapping found for the specified course and TA&amp;quot; } if ta_mapping.nil?&lt;br /&gt;
&lt;br /&gt;
  ta = User.find(ta_mapping.user_id)&lt;br /&gt;
  ta_count = TaMapping.where(user_id: user_id).size - 1&lt;br /&gt;
&lt;br /&gt;
  if ta_count.zero?&lt;br /&gt;
    ta.update(role: Role::STUDENT)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  ta_mapping.destroy&lt;br /&gt;
  { success: true, ta_name: ta.name }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 4. copy_course ====&lt;br /&gt;
Creates a duplicate of the course.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def copy_course&lt;br /&gt;
  new_course = dup&lt;br /&gt;
  new_course.directory_path += '_copy'&lt;br /&gt;
  new_course.name += '_copy'&lt;br /&gt;
  new_course.save&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Issues in Existing Code==&lt;br /&gt;
&lt;br /&gt;
=== Bugs ===&lt;br /&gt;
The following statements in the Questionnaire model have inconsistent naming as the first statement relies on the relationship &amp;quot;participants&amp;quot; while the second statement relies on the relationship &amp;quot;course_participants&amp;quot; which doesn't exist.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
has_many :participants, class_name: 'CourseParticipant', foreign_key: 'parent_id', dependent: :destroy, inverse_of: :course&lt;br /&gt;
has_many :users, through: :course_participants, inverse_of: :course&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Coverage Issues ===&lt;br /&gt;
&lt;br /&gt;
spec/models/questionnaire_spec.rb exists but only covers validations, associations, and copy_questionnaire_details. Several instance methods and the entire QuestionAdvice model have less coverage.&lt;br /&gt;
&lt;br /&gt;
spec/models/course_spec.rb only tests validations and the path method. Three instance methods - add_ta, remove_ta, and copy_course - have zero unit test coverage.&lt;br /&gt;
&lt;br /&gt;
== Test Plan ==&lt;br /&gt;
&lt;br /&gt;
For this project, we will be using factories and fixtures to generate our models for unit testing with RSpec.&lt;br /&gt;
&lt;br /&gt;
==== Questionnaire ====&lt;br /&gt;
&lt;br /&gt;
Currently for Questionnaire, the data for unit testing is currently being created using Factory so we will continue to write tests that ensure over 80% code coverage. We will also use factories for the Course model as the method logic is dependent on varying data. It allows us to adjust parameters and test for edge cases easier.&lt;br /&gt;
&lt;br /&gt;
The following code is currently how the factory for the Questionnaire model is set up.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# spec/factories/questionnaires.rb&lt;br /&gt;
FactoryBot.define do&lt;br /&gt;
  factory :questionnaire do&lt;br /&gt;
    sequence(:name) { |n| &amp;quot;Questionnaire #{n}&amp;quot; }&lt;br /&gt;
    private { false }&lt;br /&gt;
    min_question_score { 0 }&lt;br /&gt;
    max_question_score { 10 }&lt;br /&gt;
    association :instructor&lt;br /&gt;
    association :assignment&lt;br /&gt;
&lt;br /&gt;
    # Trait for questionnaire with questions&lt;br /&gt;
    trait :with_questions do&lt;br /&gt;
      after(:create) do |questionnaire|&lt;br /&gt;
        create(:item, questionnaire: questionnaire, weight: 1, seq: 1, txt: &amp;quot;que 1&amp;quot;, question_type: &amp;quot;Scale&amp;quot;)&lt;br /&gt;
        create(:item, questionnaire: questionnaire, weight: 10, seq: 2, txt: &amp;quot;que 2&amp;quot;, question_type: &amp;quot;Checkbox&amp;quot;)&lt;br /&gt;
      end&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;
The following code serves as the factory for generating instances of items which are essentially questions in a questionnaire object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# spec/factories/items.rb&lt;br /&gt;
FactoryBot.define do&lt;br /&gt;
  factory :item do&lt;br /&gt;
    sequence(:txt) { |n| &amp;quot;Question #{n}&amp;quot; }&lt;br /&gt;
    sequence(:seq) { |n| n }&lt;br /&gt;
    weight { 1 }&lt;br /&gt;
    question_type { &amp;quot;Scale&amp;quot; }&lt;br /&gt;
    break_before { true }&lt;br /&gt;
    association :questionnaire&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Course ====&lt;br /&gt;
&lt;br /&gt;
The following code is the current version of the factory for the course models.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
FactoryBot.define do&lt;br /&gt;
  factory :course do&lt;br /&gt;
    name { Faker::Educator.course_name }&lt;br /&gt;
    info { Faker::Lorem.paragraph }&lt;br /&gt;
    private { false }&lt;br /&gt;
    directory_path { Faker::File.dir }&lt;br /&gt;
    association :institution, factory: :institution&lt;br /&gt;
    association :instructor, factory: :user&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== QuestionAdvice ====&lt;br /&gt;
&lt;br /&gt;
For QuestionAdvice use fixtures to load data into database to test that it is being saved and exported properly. As it stands, there isn't that much logical complexity with the data inside QuestionAdvice itself as it is more concerned with exporting data into csv or json files for storage. The fixtures are stored in question_advice.yml under the fixtures folder.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#question_advice.yml&lt;br /&gt;
&lt;br /&gt;
question_advice_1:  &lt;br /&gt;
  id: 1&lt;br /&gt;
  question_id: 1&lt;br /&gt;
  score: 5&lt;br /&gt;
  advice: &amp;quot;This is some advice for question 1.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
question_advice_2:&lt;br /&gt;
  id: 2&lt;br /&gt;
  question_id: 2&lt;br /&gt;
  score: 3&lt;br /&gt;
  advice: &amp;quot;This is some advice for question 2.&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Coverage and Current Results ==&lt;br /&gt;
SimpleCov report for models under test can be found here.&lt;br /&gt;
&lt;br /&gt;
Test Results:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! File !! % covered !! Lines !! Relevant Lines !! Lines covered !! Lines missed !! Avg. Hits / Line&lt;br /&gt;
|-&lt;br /&gt;
| app/models/questionnaire.rb || 64.06% || 141 || 64 || 41 || 23 || 3.02&lt;br /&gt;
|-&lt;br /&gt;
| app/models/question_advice.rb || 38.46% || 24 || 13 || 5 || 8 || 0.38&lt;br /&gt;
|-&lt;br /&gt;
| app/models/course.rb || 43.59% || 59 || 39 || 17 || 22 || 0.46&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
This section will be updated with final results at the end of the project.&lt;br /&gt;
&lt;br /&gt;
== Github == &lt;br /&gt;
Link to Github Project repo: [https://github.com/MATTMINWIN/reimplementation-back-end]&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Djhanse2</name></author>
	</entry>
</feed>