<?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=Kdnadkar</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=Kdnadkar"/>
	<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=Special:Contributions/Kdnadkar"/>
	<updated>2026-05-22T13:52:04Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.0</generator>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Export-sequence.png&amp;diff=168140</id>
		<title>File:Export-sequence.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Export-sequence.png&amp;diff=168140"/>
		<updated>2026-04-28T04:12:52Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: Kdnadkar uploaded a new version of File:Export-sequence.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-sequence.png&amp;diff=168139</id>
		<title>File:Import-sequence.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-sequence.png&amp;diff=168139"/>
		<updated>2026-04-28T04:12:45Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: Kdnadkar uploaded a new version of File:Import-sequence.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-export-architecture.png&amp;diff=168138</id>
		<title>File:Import-export-architecture.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-export-architecture.png&amp;diff=168138"/>
		<updated>2026-04-28T04:12:22Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: Kdnadkar uploaded a new version of File:Import-export-architecture.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=168137</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=168137"/>
		<updated>2026-04-28T04:12:09Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project Design Document: E2606 Import/Export =&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
The E2606 import/export work builds on the shared import/export framework introduced by [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. The final design keeps the reusable pipeline, but applies it to practical instructor-facing workflows such as users, course participants, assignment participants, teams, topics, questionnaires, and grades. The goal is to let instructors move data in and out reliably without exposing internal database fields or maintaining one-off CSV scripts for each entity.&lt;br /&gt;
&lt;br /&gt;
The central design distinction is that '''importable''' and '''exportable''' are related but not identical capabilities:&lt;br /&gt;
&lt;br /&gt;
* An '''importable''' class can create or update application records from uploaded CSV data. It must define required fields, optional fields, external lookup fields, request context, and duplicate handling behavior.&lt;br /&gt;
* An '''exportable''' class can produce CSV data from existing application records. It may expose calculated, flattened, or reporting-oriented fields that should not necessarily be accepted during import.&lt;br /&gt;
* Some workflows, such as grades and questionnaire packages, are intentionally specialized instead of using the generic single-model path.&lt;br /&gt;
&lt;br /&gt;
== Previous Work ==&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. E2560 introduced the shared import/export framework that replaced one-off entity-specific CSV logic with reusable model metadata, service-layer CSV processing, field mapping, and duplicate-handling strategies.&lt;br /&gt;
&lt;br /&gt;
E2606 extends that foundation by applying the framework to additional class types and by tightening the design around importable versus exportable classes, hidden/system-managed fields, assignment-scoped data, questionnaire packages, and grade export.&lt;br /&gt;
&lt;br /&gt;
== Team ==&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Isaac Martin (iemartin@ncsu.edu)&lt;br /&gt;
&lt;br /&gt;
== Problem Statement ==&lt;br /&gt;
&lt;br /&gt;
The previous generic import/export framework made the system more reusable, but it still needed refinement before it could support the full set of entities expected by instructors. The main problems were:&lt;br /&gt;
&lt;br /&gt;
* The system needed clearer boundaries around which classes can be imported or exported.&lt;br /&gt;
* User-facing field lists needed to hide internal or system-managed columns such as IDs, timestamps, and foreign keys.&lt;br /&gt;
* Multi-table workflows such as teams with members and questionnaires with items/advice needed special handling.&lt;br /&gt;
* Scoped data, such as teams, assignment participants, and course participants, needed request context so imports could follow the same rules as the application.&lt;br /&gt;
* Grades needed to be treated as an export-only reporting workflow rather than as a normal mutable model.&lt;br /&gt;
&lt;br /&gt;
This design addresses those issues by combining a shared import/export pipeline with explicit class support, hidden fields, context-aware model behavior, and specialized services where the generic model path is not a good fit.&lt;br /&gt;
&lt;br /&gt;
== Capability Matrix ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Class or workflow&lt;br /&gt;
! Importable&lt;br /&gt;
! Exportable&lt;br /&gt;
! Generic endpoint?&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| User&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Imports use role and institution external lookups. Exports omit hidden fields.&lt;br /&gt;
|-&lt;br /&gt;
| CourseParticipant&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Requires course_id context. CSV intentionally contains user_name only.&lt;br /&gt;
|-&lt;br /&gt;
| Team&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Requires assignment_id context. Exports dynamic participant_1 through participant_n columns.&lt;br /&gt;
|-&lt;br /&gt;
| AssignmentParticipant&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Requires assignment_id context. CSV intentionally contains user_name only.&lt;br /&gt;
|-&lt;br /&gt;
| ProjectTopic&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Assignment-backed topic records.&lt;br /&gt;
|-&lt;br /&gt;
| Questionnaire*&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Generic model metadata exists, but template movement is handled with Item and QuestionAdvice through the package workflow.&lt;br /&gt;
|-&lt;br /&gt;
| Item*&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Represents questionnaire questions/items and participates in the questionnaire package workflow.&lt;br /&gt;
|-&lt;br /&gt;
| QuestionAdvice*&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Represents scoring advice associated with questionnaire items and participates in the questionnaire package workflow.&lt;br /&gt;
|-&lt;br /&gt;
| Grades&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| No, specialized&lt;br /&gt;
| Export-only reporting workflow. No import path because grades are derived or instructor-assigned data.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; Questionnaire, Item, and QuestionAdvice can be treated as individual import/export classes, but the preferred template workflow handles them together as a multi-file or zip package so their relationships are preserved.&lt;br /&gt;
&lt;br /&gt;
== High-Level Architecture ==&lt;br /&gt;
&lt;br /&gt;
[[File:Import-export-architecture.png|thumb|center|700px|Generic import/export framework with specialized questionnaire and grades paths]]&lt;br /&gt;
&lt;br /&gt;
The architecture diagram is intended to depict the generic framework, with specialized questionnaire and grades workflows shown as side paths. The reusable core is organized around two generic controllers and two service objects:&lt;br /&gt;
&lt;br /&gt;
* '''ImportController''' exposes metadata and upload endpoints for supported import classes.&lt;br /&gt;
* '''ExportController''' exposes metadata and download endpoints for supported export classes.&lt;br /&gt;
* '''Import''' coordinates CSV import, model row processing, and duplicate handling.&lt;br /&gt;
* '''Export''' coordinates CSV generation and preserves requested field order.&lt;br /&gt;
* '''FieldMapping''' normalizes CSV headers and selected fields into the model's internal field names.&lt;br /&gt;
* '''ImportableExportableHelper''' is the model-level declaration layer for mandatory fields, optional fields, hidden fields, external fields, filters, and import row behavior.&lt;br /&gt;
* Questionnaire package services handle multi-file questionnaire template export/import outside the generic single-model export path.&lt;br /&gt;
&lt;br /&gt;
One important improvement is explicit class support. Instead of blindly resolving any client-provided class name, the controllers resolve requests through supported import/export class lists. This makes the API safer and makes the supported surface easier to test and document.&lt;br /&gt;
&lt;br /&gt;
== Importable Classes ==&lt;br /&gt;
&lt;br /&gt;
Importable classes must be safe to mutate from CSV input. A class is importable only when it can define the following:&lt;br /&gt;
&lt;br /&gt;
* Mandatory CSV columns&lt;br /&gt;
* Optional CSV columns&lt;br /&gt;
* Hidden or system-managed columns&lt;br /&gt;
* External lookup columns for related records&lt;br /&gt;
* Whether referenced records should be looked up, created, or rejected if missing&lt;br /&gt;
* Duplicate handling options&lt;br /&gt;
* Any request context required to make the import safe, such as assignment_id or course_id&lt;br /&gt;
&lt;br /&gt;
The generic import flow is:&lt;br /&gt;
&lt;br /&gt;
# ImportController receives class, csv_file, use_headers, ordered_fields, duplicate action, and optional context.&lt;br /&gt;
# The controller resolves the requested class from the supported import class list.&lt;br /&gt;
# Import delegates row processing to klass.try_import_records.&lt;br /&gt;
# FieldMapping normalizes selected or header-provided columns.&lt;br /&gt;
# ImportableExportableHelper builds model instances, resolves external fields, applies defaults, saves records, and returns duplicates.&lt;br /&gt;
&lt;br /&gt;
[[File:Import-sequence.png|thumb|center|700px|Generic import request sequence]]&lt;br /&gt;
&lt;br /&gt;
Special import behavior exists where a raw table-shaped import would be unsafe:&lt;br /&gt;
&lt;br /&gt;
* Team import requires assignment context and creates AssignmentTeam rows with participant links.&lt;br /&gt;
* AssignmentParticipant import accepts usernames only and attaches existing users to an assignment.&lt;br /&gt;
* CourseParticipant import accepts usernames only and attaches existing users to a course.&lt;br /&gt;
* QuestionnairePackageImportService imports related files together because questionnaire templates are graph-shaped rather than single-table data.&lt;br /&gt;
&lt;br /&gt;
== Exportable Classes ==&lt;br /&gt;
&lt;br /&gt;
Exportable classes are read-only from the user's perspective. They do not need duplicate handling, and they may expose calculated or flattened fields that are useful in CSV output.&lt;br /&gt;
&lt;br /&gt;
The generic export flow is:&lt;br /&gt;
&lt;br /&gt;
# ExportController receives class, ordered_fields, and optional context.&lt;br /&gt;
# The controller resolves the requested class from the supported export class list.&lt;br /&gt;
# Export.perform exports the class directly. Specialized workflows such as questionnaire packages and grades use their own services/controllers.&lt;br /&gt;
# FieldMapping preserves the selected column order.&lt;br /&gt;
# The frontend receives one or more CSV payloads and downloads them.&lt;br /&gt;
&lt;br /&gt;
[[File:Export-sequence.png|thumb|center|700px|Generic export request sequence]]&lt;br /&gt;
&lt;br /&gt;
Some exports intentionally avoid the generic path:&lt;br /&gt;
&lt;br /&gt;
* Grades export is handled by GradesController#export because it is assignment-scoped and gradebook-oriented.&lt;br /&gt;
* Questionnaire package export uses QuestionnairePackageExportService because it produces a zip containing multiple related CSV files.&lt;br /&gt;
&lt;br /&gt;
== Hidden Fields and Field Visibility ==&lt;br /&gt;
&lt;br /&gt;
A major usability issue in generic import/export systems is exposing too many database fields. End users should not need to see or map internal IDs, timestamps, tokens, or foreign keys that are managed by the application.&lt;br /&gt;
&lt;br /&gt;
The design uses hidden fields to remove system-managed fields from the metadata returned to the frontend. Models still know those fields exist, but the import/export UI only receives the fields that are relevant to users.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a field is marked as hidden, it is removed from the user-facing mandatory and optional field lists even if it exists in the database schema or model metadata.&lt;br /&gt;
&lt;br /&gt;
Examples include:&lt;br /&gt;
&lt;br /&gt;
* id&lt;br /&gt;
* created_at&lt;br /&gt;
* updated_at&lt;br /&gt;
* instructor_id&lt;br /&gt;
* institution_id&lt;br /&gt;
* course_id&lt;br /&gt;
&lt;br /&gt;
For CSV-facing relationships, the design prefers readable fields such as course_name and user_name.&lt;br /&gt;
&lt;br /&gt;
== Duplicate Handling ==&lt;br /&gt;
&lt;br /&gt;
Import supports strategy-based duplicate handling. The available strategies can vary by entity, but the shared model metadata lets the frontend display the available options before import.&lt;br /&gt;
&lt;br /&gt;
The main duplicate actions are:&lt;br /&gt;
&lt;br /&gt;
* '''SkipRecordAction''': ignore incoming rows that conflict with existing records.&lt;br /&gt;
* '''UpdateExistingRecordAction''': update the existing record with incoming values.&lt;br /&gt;
* '''ChangeOffendingFieldAction''': modify the conflicting field to make the incoming record unique.&lt;br /&gt;
&lt;br /&gt;
This keeps duplicate behavior explicit. It also allows sensitive workflows to restrict the available actions. For example, participant import is intentionally narrower than user import because it should attach existing users to an assignment or course, not silently create or rewrite user accounts.&lt;br /&gt;
&lt;br /&gt;
== Specialized Workflows ==&lt;br /&gt;
&lt;br /&gt;
=== Assignment Participants ===&lt;br /&gt;
&lt;br /&gt;
Assignment participant import/export is intentionally narrower than a normal model export. The CSV exposes only user_name, and the request must include assignment_id.&lt;br /&gt;
&lt;br /&gt;
This prevents participant import from accidentally creating or editing users. Import resolves each username against existing User records, then creates or reuses the AssignmentParticipant link for the selected assignment.&lt;br /&gt;
&lt;br /&gt;
This design keeps team and participant membership tied to the correct assignment scope, matching the same conceptual rules used when adding participants through the application.&lt;br /&gt;
&lt;br /&gt;
The exported filename includes the assignment name and ID, while the CSV body stays username-only so it can be imported again without extra cleanup.&lt;br /&gt;
&lt;br /&gt;
=== Course Participants ===&lt;br /&gt;
&lt;br /&gt;
Course participant import/export follows the same safety boundary as assignment participants. The CSV exposes only user_name, and the request must include course_id.&lt;br /&gt;
&lt;br /&gt;
This prevents participant import from accidentally creating or editing users. Import resolves each username against existing User records, then creates or reuses the CourseParticipant link for the selected course.&lt;br /&gt;
&lt;br /&gt;
This design keeps course roster membership tied to the correct course scope, matching the same conceptual rules used when adding participants through the application.&lt;br /&gt;
&lt;br /&gt;
The exported filename includes the course name and ID, while the CSV body stays username-only so it can be imported again without extra cleanup.&lt;br /&gt;
&lt;br /&gt;
=== Teams ===&lt;br /&gt;
&lt;br /&gt;
Team import/export also requires assignment context. The export surface is flattened:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name,participant_1,participant_2,participant_3,...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The number of participant columns is based on assignment size when available, otherwise it falls back to a default count. On import, the team name identifies the team and the participant columns identify assignment participants to attach to that team.&lt;br /&gt;
&lt;br /&gt;
The frontend may display this as a single participant_ids option while expanding it back to the backend's individual participant columns. This keeps the UI compact without changing the backend CSV contract.&lt;br /&gt;
&lt;br /&gt;
=== Topics ===&lt;br /&gt;
&lt;br /&gt;
Project topics are assignment-backed records. They can use the same generic import/export path because they are closer to single-model data than teams or questionnaire packages.&lt;br /&gt;
&lt;br /&gt;
The important design point is naming clarity. The implementation uses ProjectTopic rather than the older sign-up topic naming, so the import/export documentation and endpoints should use the current class name.&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire Packages ===&lt;br /&gt;
&lt;br /&gt;
Questionnaire templates are not just single rows in a questionnaires table. A useful questionnaire export must preserve related items and advice. Because of that, questionnaire templates use a package workflow in addition to the generic model path.&lt;br /&gt;
&lt;br /&gt;
The package controller and services handle:&lt;br /&gt;
&lt;br /&gt;
* Exporting questionnaires, items, and advice together&lt;br /&gt;
* Importing either a zip package or separate CSV files&lt;br /&gt;
* Downloading blank templates&lt;br /&gt;
* Previewing import effects before committing&lt;br /&gt;
* Applying duplicate actions through a package-specific allowlist&lt;br /&gt;
&lt;br /&gt;
This keeps questionnaire import/export understandable and avoids forcing a multi-table template into a single CSV.&lt;br /&gt;
&lt;br /&gt;
=== Grades ===&lt;br /&gt;
&lt;br /&gt;
Grades are export-only. They are not imported through the generic framework because grade data is either computed from related review data or assigned by instructors in application workflows.&lt;br /&gt;
&lt;br /&gt;
The export is assignment-scoped and gradebook-oriented. It uses fixed columns such as:&lt;br /&gt;
&lt;br /&gt;
* username&lt;br /&gt;
* grade&lt;br /&gt;
* comment&lt;br /&gt;
* optional email&lt;br /&gt;
&lt;br /&gt;
The View Scores page reuses the grades export endpoint so the on-screen table and downloaded CSV are based on the same username, grade, comment, and optional email data.&lt;br /&gt;
&lt;br /&gt;
This also avoids confusing instructor-assigned marks with computed peer-review, teammate-review, or author-feedback aggregates.&lt;br /&gt;
&lt;br /&gt;
== Questionnaire Packages ==&lt;br /&gt;
&lt;br /&gt;
Some data cannot be represented well by exporting a single model alone. The main example is questionnaire template data, where a useful export may need the questionnaire, its items, and related advice.&lt;br /&gt;
&lt;br /&gt;
The final design uses explicit package services for this workflow. This is clearer than generic relationship traversal because it defines exactly which files belong in the export, supports zip-based transfer, provides blank templates, and supports preview before import.&lt;br /&gt;
&lt;br /&gt;
For the final wiki, the important design point is that multi-table exports are handled deliberately rather than forcing every related record into a single CSV.&lt;br /&gt;
&lt;br /&gt;
== Frontend Design ==&lt;br /&gt;
&lt;br /&gt;
The frontend uses reusable modals for most generic classes:&lt;br /&gt;
&lt;br /&gt;
* '''ImportModal''' fetches metadata from /import/:class, previews CSV rows, lets users map columns when headers are absent, sends duplicate-action choices, and forwards context parameters such as assignment_id or course_id.&lt;br /&gt;
* '''ExportModal''' fetches metadata from /export/:class, lets users choose and order columns, sends the selected order, and downloads returned CSV payloads.&lt;br /&gt;
&lt;br /&gt;
The import modal supports:&lt;br /&gt;
&lt;br /&gt;
* CSV file selection&lt;br /&gt;
* Header-based import&lt;br /&gt;
* Manual column-to-field mapping&lt;br /&gt;
* Preview before confirmation&lt;br /&gt;
* Duplicate action selection&lt;br /&gt;
* Sample CSV download&lt;br /&gt;
&lt;br /&gt;
Specialized pages use custom controls where the generic modal is not enough:&lt;br /&gt;
&lt;br /&gt;
* Questionnaire package modals support zip export/import, separate CSV uploads, preview, and template downloads.&lt;br /&gt;
* Grades export is launched from the grades or assignment workflow rather than the generic export modal.&lt;br /&gt;
* The View Scores page reads from the grades export endpoint to display the same saved grade data that export downloads.&lt;br /&gt;
&lt;br /&gt;
== Design Rationale ==&lt;br /&gt;
&lt;br /&gt;
The design separates class capability from class existence. Open-ended class resolution can expose models simply because the client supplies a class name. Explicit supported import and export class lists make behavior visible, testable, and safer.&lt;br /&gt;
&lt;br /&gt;
The system also avoids assuming every exportable thing should be importable. Grades are a reporting artifact, so they are export-only. Questionnaire packages are graph-shaped, so they use a package service. Assignment participants, course participants, and teams need request context, so they override the generic table-shaped behavior.&lt;br /&gt;
&lt;br /&gt;
This produces a hybrid design:&lt;br /&gt;
&lt;br /&gt;
* Generic where the data is model-shaped and safe to process uniformly&lt;br /&gt;
* Specialized where the data is contextual, graph-shaped, computed, or safety-sensitive&lt;br /&gt;
&lt;br /&gt;
== Testing and Verification ==&lt;br /&gt;
&lt;br /&gt;
The implementation should be verified at three levels:&lt;br /&gt;
&lt;br /&gt;
* Model and helper specs for metadata, hidden fields, package export/import, and row-level import behavior&lt;br /&gt;
* Request specs for metadata endpoints, import endpoints, export endpoints, unsupported classes, and missing context&lt;br /&gt;
* Frontend tests or manual verification for modal metadata loading, preview, duplicate action selection, and generated downloads&lt;br /&gt;
&lt;br /&gt;
Important cases include:&lt;br /&gt;
&lt;br /&gt;
* Unsupported class names are rejected&lt;br /&gt;
* Missing assignment_id is rejected for assignment-scoped teams and participants&lt;br /&gt;
* Missing course_id is rejected for course participants&lt;br /&gt;
* Missing referenced records produce readable validation errors&lt;br /&gt;
* Duplicate rows use the selected duplicate strategy&lt;br /&gt;
* Questionnaire package preview reports intended actions before import&lt;br /&gt;
* Grade export is limited to the selected assignment and teaching-staff permissions&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=168103</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=168103"/>
		<updated>2026-04-27T02:27:05Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project Design Document: E2606 Import/Export =&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
The E2606 import/export work builds on the shared import/export framework introduced by [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. The final design keeps the reusable pipeline, but applies it to practical instructor-facing workflows such as users, courses, assignments, teams, participants, topics, questionnaires, and grades. The goal is to let instructors move data in and out reliably without exposing internal database fields or maintaining one-off CSV scripts for each entity.&lt;br /&gt;
&lt;br /&gt;
The central design distinction is that '''importable''' and '''exportable''' are related but not identical capabilities:&lt;br /&gt;
&lt;br /&gt;
* An '''importable''' class can create or update application records from uploaded CSV data. It must define required fields, optional fields, external lookup fields, request context, and duplicate handling behavior.&lt;br /&gt;
* An '''exportable''' class can produce CSV data from existing application records. It may expose calculated, flattened, or reporting-oriented fields that should not necessarily be accepted during import.&lt;br /&gt;
* Some workflows, such as grades and questionnaire packages, are intentionally specialized instead of using the generic single-model path.&lt;br /&gt;
&lt;br /&gt;
== Previous Work ==&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. E2560 introduced the shared import/export framework that replaced one-off entity-specific CSV logic with reusable model metadata, service-layer CSV processing, field mapping, and duplicate-handling strategies.&lt;br /&gt;
&lt;br /&gt;
E2606 extends that foundation by applying the framework to additional class types and by tightening the design around importable versus exportable classes, hidden/system-managed fields, assignment-scoped data, questionnaire packages, and grade export.&lt;br /&gt;
&lt;br /&gt;
== Team ==&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Isaac Martin (iemartin@ncsu.edu)&lt;br /&gt;
&lt;br /&gt;
== Problem Statement ==&lt;br /&gt;
&lt;br /&gt;
The previous generic import/export framework made the system more reusable, but it still needed refinement before it could support the full set of entities expected by instructors. The main problems were:&lt;br /&gt;
&lt;br /&gt;
* The system needed clearer boundaries around which classes can be imported or exported.&lt;br /&gt;
* User-facing field lists needed to hide internal or system-managed columns such as IDs, timestamps, and foreign keys.&lt;br /&gt;
* Multi-table workflows such as teams with members and questionnaires with items/advice needed special handling.&lt;br /&gt;
* Assignment-scoped data, such as teams and assignment participants, needed request context so imports could follow the same rules as the application.&lt;br /&gt;
* Grades needed to be treated as an export-only reporting workflow rather than as a normal mutable model.&lt;br /&gt;
&lt;br /&gt;
This design addresses those issues by combining a shared import/export pipeline with explicit class support, hidden fields, context-aware model behavior, and specialized services where the generic model path is not a good fit.&lt;br /&gt;
&lt;br /&gt;
== Capability Matrix ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Class or workflow&lt;br /&gt;
! Importable&lt;br /&gt;
! Exportable&lt;br /&gt;
! Generic endpoint?&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| User&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Imports use role and institution external lookups. Exports omit hidden fields.&lt;br /&gt;
|-&lt;br /&gt;
| Course&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Uses instructor_name and institution_name as CSV-facing fields instead of raw foreign keys.&lt;br /&gt;
|-&lt;br /&gt;
| Assignment&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Uses instructor_name and optional course_name instead of raw IDs.&lt;br /&gt;
|-&lt;br /&gt;
| Team&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Requires assignment_id context. Exports dynamic participant_1 through participant_n columns.&lt;br /&gt;
|-&lt;br /&gt;
| AssignmentParticipant&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Requires assignment_id context. CSV intentionally contains user_name only.&lt;br /&gt;
|-&lt;br /&gt;
| ProjectTopic&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Assignment-backed topic records.&lt;br /&gt;
|-&lt;br /&gt;
| Questionnaire*&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Generic model metadata exists, but template movement is handled with Item and QuestionAdvice through the package workflow.&lt;br /&gt;
|-&lt;br /&gt;
| Item*&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Represents questionnaire questions/items and participates in the questionnaire package workflow.&lt;br /&gt;
|-&lt;br /&gt;
| QuestionAdvice*&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Represents scoring advice associated with questionnaire items and participates in the questionnaire package workflow.&lt;br /&gt;
|-&lt;br /&gt;
| Answer&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Supported by the generic metadata model.&lt;br /&gt;
|-&lt;br /&gt;
| QuizItem&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Supported by the generic metadata model.&lt;br /&gt;
|-&lt;br /&gt;
| Grades&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| No, specialized&lt;br /&gt;
| Export-only reporting workflow. No import path because grades are derived or instructor-assigned data.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; Questionnaire, Item, and QuestionAdvice can be treated as individual import/export classes, but the preferred template workflow handles them together as a multi-file or zip package so their relationships are preserved.&lt;br /&gt;
&lt;br /&gt;
== High-Level Architecture ==&lt;br /&gt;
&lt;br /&gt;
[[File:Import-export-architecture.png|thumb|center|700px|Generic import/export framework with specialized questionnaire and grades paths]]&lt;br /&gt;
&lt;br /&gt;
The architecture diagram is intended to depict the generic framework, with specialized questionnaire and grades workflows shown as side paths. The reusable core is organized around two generic controllers and two service objects:&lt;br /&gt;
&lt;br /&gt;
* '''ImportController''' exposes metadata and upload endpoints for supported import classes.&lt;br /&gt;
* '''ExportController''' exposes metadata and download endpoints for supported export classes.&lt;br /&gt;
* '''Import''' coordinates CSV import, model row processing, and duplicate handling.&lt;br /&gt;
* '''Export''' coordinates CSV generation and preserves requested field order.&lt;br /&gt;
* '''FieldMapping''' normalizes CSV headers and selected fields into the model's internal field names.&lt;br /&gt;
* '''ImportableExportableHelper''' is the model-level declaration layer for mandatory fields, optional fields, hidden fields, external fields, filters, and import row behavior.&lt;br /&gt;
* Questionnaire package services handle multi-file questionnaire template export/import outside the generic single-model export path.&lt;br /&gt;
&lt;br /&gt;
One important improvement is explicit class support. Instead of blindly resolving any client-provided class name, the controllers resolve requests through supported import/export class lists. This makes the API safer and makes the supported surface easier to test and document.&lt;br /&gt;
&lt;br /&gt;
== Importable Classes ==&lt;br /&gt;
&lt;br /&gt;
Importable classes must be safe to mutate from CSV input. A class is importable only when it can define the following:&lt;br /&gt;
&lt;br /&gt;
* Mandatory CSV columns&lt;br /&gt;
* Optional CSV columns&lt;br /&gt;
* Hidden or system-managed columns&lt;br /&gt;
* External lookup columns for related records&lt;br /&gt;
* Whether referenced records should be looked up, created, or rejected if missing&lt;br /&gt;
* Duplicate handling options&lt;br /&gt;
* Any request context required to make the import safe, such as assignment_id&lt;br /&gt;
&lt;br /&gt;
The generic import flow is:&lt;br /&gt;
&lt;br /&gt;
# ImportController receives class, csv_file, use_headers, ordered_fields, duplicate action, and optional context.&lt;br /&gt;
# The controller resolves the requested class from the supported import class list.&lt;br /&gt;
# Import delegates row processing to klass.try_import_records.&lt;br /&gt;
# FieldMapping normalizes selected or header-provided columns.&lt;br /&gt;
# ImportableExportableHelper builds model instances, resolves external fields, applies defaults, saves records, and returns duplicates.&lt;br /&gt;
&lt;br /&gt;
[[File:Import-sequence.png|thumb|center|700px|Generic import request sequence]]&lt;br /&gt;
&lt;br /&gt;
Special import behavior exists where a raw table-shaped import would be unsafe:&lt;br /&gt;
&lt;br /&gt;
* Team import requires assignment context and creates AssignmentTeam rows with participant links.&lt;br /&gt;
* AssignmentParticipant import accepts usernames only and attaches existing users to an assignment.&lt;br /&gt;
* Course and Assignment import use human-readable association fields, such as instructor_name and institution_name, instead of raw foreign keys.&lt;br /&gt;
* QuestionnairePackageImportService imports related files together because questionnaire templates are graph-shaped rather than single-table data.&lt;br /&gt;
&lt;br /&gt;
== Exportable Classes ==&lt;br /&gt;
&lt;br /&gt;
Exportable classes are read-only from the user's perspective. They do not need duplicate handling, and they may expose calculated or flattened fields that are useful in CSV output.&lt;br /&gt;
&lt;br /&gt;
The generic export flow is:&lt;br /&gt;
&lt;br /&gt;
# ExportController receives class, ordered_fields, and optional context.&lt;br /&gt;
# The controller resolves the requested class from the supported export class list.&lt;br /&gt;
# Export.perform exports the class directly. Specialized workflows such as questionnaire packages and grades use their own services/controllers.&lt;br /&gt;
# FieldMapping preserves the selected column order.&lt;br /&gt;
# The frontend receives one or more CSV payloads and downloads them.&lt;br /&gt;
&lt;br /&gt;
[[File:Export-sequence.png|thumb|center|700px|Generic export request sequence]]&lt;br /&gt;
&lt;br /&gt;
Some exports intentionally avoid the generic path:&lt;br /&gt;
&lt;br /&gt;
* Grades export is handled by GradesController#export because it is assignment-scoped and gradebook-oriented.&lt;br /&gt;
* Questionnaire package export uses QuestionnairePackageExportService because it produces a zip containing multiple related CSV files.&lt;br /&gt;
&lt;br /&gt;
== Hidden Fields and Field Visibility ==&lt;br /&gt;
&lt;br /&gt;
A major usability issue in generic import/export systems is exposing too many database fields. End users should not need to see or map internal IDs, timestamps, tokens, or foreign keys that are managed by the application.&lt;br /&gt;
&lt;br /&gt;
The design uses hidden fields to remove system-managed fields from the metadata returned to the frontend. Models still know those fields exist, but the import/export UI only receives the fields that are relevant to users.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a field is marked as hidden, it is removed from the user-facing mandatory and optional field lists even if it exists in the database schema or model metadata.&lt;br /&gt;
&lt;br /&gt;
Examples include:&lt;br /&gt;
&lt;br /&gt;
* id&lt;br /&gt;
* created_at&lt;br /&gt;
* updated_at&lt;br /&gt;
* instructor_id&lt;br /&gt;
* institution_id&lt;br /&gt;
* course_id&lt;br /&gt;
&lt;br /&gt;
For CSV-facing relationships, the design prefers readable fields such as instructor_name, institution_name, course_name, and user_name.&lt;br /&gt;
&lt;br /&gt;
== Duplicate Handling ==&lt;br /&gt;
&lt;br /&gt;
Import supports strategy-based duplicate handling. The available strategies can vary by entity, but the shared model metadata lets the frontend display the available options before import.&lt;br /&gt;
&lt;br /&gt;
The main duplicate actions are:&lt;br /&gt;
&lt;br /&gt;
* '''SkipRecordAction''': ignore incoming rows that conflict with existing records.&lt;br /&gt;
* '''UpdateExistingRecordAction''': update the existing record with incoming values.&lt;br /&gt;
* '''ChangeOffendingFieldAction''': modify the conflicting field to make the incoming record unique.&lt;br /&gt;
&lt;br /&gt;
This keeps duplicate behavior explicit. It also allows sensitive workflows to restrict the available actions. For example, assignment participant import is intentionally narrower than user import because it should attach existing users to an assignment, not silently create or rewrite user accounts.&lt;br /&gt;
&lt;br /&gt;
== Specialized Workflows ==&lt;br /&gt;
&lt;br /&gt;
=== Courses and Assignments ===&lt;br /&gt;
&lt;br /&gt;
Courses and assignments avoid exposing raw foreign keys in CSVs:&lt;br /&gt;
&lt;br /&gt;
* Course uses instructor_name and institution_name.&lt;br /&gt;
* Assignment uses instructor_name and optional course_name.&lt;br /&gt;
&lt;br /&gt;
Before validation, imported names are resolved into actual associations. If a referenced instructor, institution, or course cannot be found, validation fails with a readable error.&lt;br /&gt;
&lt;br /&gt;
This keeps CSV files understandable to instructors while still preserving normalized database relationships internally.&lt;br /&gt;
&lt;br /&gt;
=== Assignment Participants ===&lt;br /&gt;
&lt;br /&gt;
Assignment participant import/export is intentionally narrower than a normal model export. The CSV exposes only user_name, and the request must include assignment_id.&lt;br /&gt;
&lt;br /&gt;
This prevents participant import from accidentally creating or editing users. Import resolves each username against existing User records, then creates or reuses the AssignmentParticipant link for the selected assignment.&lt;br /&gt;
&lt;br /&gt;
This design keeps team and participant membership tied to the correct assignment scope, matching the same conceptual rules used when adding participants through the application.&lt;br /&gt;
&lt;br /&gt;
=== Teams ===&lt;br /&gt;
&lt;br /&gt;
Team import/export also requires assignment context. The export surface is flattened:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name,participant_1,participant_2,participant_3,...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The number of participant columns is based on assignment size when available, otherwise it falls back to a default count. On import, the team name identifies the team and the participant columns identify assignment participants to attach to that team.&lt;br /&gt;
&lt;br /&gt;
The frontend may display this as a single participant_ids option while expanding it back to the backend's individual participant columns. This keeps the UI compact without changing the backend CSV contract.&lt;br /&gt;
&lt;br /&gt;
=== Topics ===&lt;br /&gt;
&lt;br /&gt;
Project topics are assignment-backed records. They can use the same generic import/export path because they are closer to single-model data than teams or questionnaire packages.&lt;br /&gt;
&lt;br /&gt;
The important design point is naming clarity. The implementation uses ProjectTopic rather than the older sign-up topic naming, so the import/export documentation and endpoints should use the current class name.&lt;br /&gt;
&lt;br /&gt;
=== Questionnaire Packages ===&lt;br /&gt;
&lt;br /&gt;
Questionnaire templates are not just single rows in a questionnaires table. A useful questionnaire export must preserve related items and advice. Because of that, questionnaire templates use a package workflow in addition to the generic model path.&lt;br /&gt;
&lt;br /&gt;
The package controller and services handle:&lt;br /&gt;
&lt;br /&gt;
* Exporting questionnaires, items, and advice together&lt;br /&gt;
* Importing either a zip package or separate CSV files&lt;br /&gt;
* Downloading blank templates&lt;br /&gt;
* Previewing import effects before committing&lt;br /&gt;
* Applying duplicate actions through a package-specific allowlist&lt;br /&gt;
&lt;br /&gt;
This keeps questionnaire import/export understandable and avoids forcing a multi-table template into a single CSV.&lt;br /&gt;
&lt;br /&gt;
=== Grades ===&lt;br /&gt;
&lt;br /&gt;
Grades are export-only. They are not imported through the generic framework because grade data is either computed from related review data or assigned by instructors in application workflows.&lt;br /&gt;
&lt;br /&gt;
The export is assignment-scoped and gradebook-oriented. It uses fixed columns such as:&lt;br /&gt;
&lt;br /&gt;
* username&lt;br /&gt;
* grade&lt;br /&gt;
* comment&lt;br /&gt;
* optional email&lt;br /&gt;
&lt;br /&gt;
This also avoids confusing instructor-assigned marks with computed peer-review, teammate-review, or author-feedback aggregates.&lt;br /&gt;
&lt;br /&gt;
== Questionnaire Packages ==&lt;br /&gt;
&lt;br /&gt;
Some data cannot be represented well by exporting a single model alone. The main example is questionnaire template data, where a useful export may need the questionnaire, its items, and related advice.&lt;br /&gt;
&lt;br /&gt;
The final design uses explicit package services for this workflow. This is clearer than generic relationship traversal because it defines exactly which files belong in the export, supports zip-based transfer, provides blank templates, and supports preview before import.&lt;br /&gt;
&lt;br /&gt;
For the final wiki, the important design point is that multi-table exports are handled deliberately rather than forcing every related record into a single CSV.&lt;br /&gt;
&lt;br /&gt;
== Frontend Design ==&lt;br /&gt;
&lt;br /&gt;
The frontend uses reusable modals for most generic classes:&lt;br /&gt;
&lt;br /&gt;
* '''ImportModal''' fetches metadata from /import/:class, previews CSV rows, lets users map columns when headers are absent, sends duplicate-action choices, and forwards context parameters such as assignment_id.&lt;br /&gt;
* '''ExportModal''' fetches metadata from /export/:class, lets users choose and order columns, sends the selected order, and downloads returned CSV payloads.&lt;br /&gt;
&lt;br /&gt;
The import modal supports:&lt;br /&gt;
&lt;br /&gt;
* CSV file selection&lt;br /&gt;
* Header-based import&lt;br /&gt;
* Manual column-to-field mapping&lt;br /&gt;
* Preview before confirmation&lt;br /&gt;
* Duplicate action selection&lt;br /&gt;
* Sample CSV download&lt;br /&gt;
&lt;br /&gt;
Specialized pages use custom controls where the generic modal is not enough:&lt;br /&gt;
&lt;br /&gt;
* Questionnaire package modals support zip export/import, separate CSV uploads, preview, and template downloads.&lt;br /&gt;
* Grades export is launched from the grades or assignment workflow rather than the generic export modal.&lt;br /&gt;
&lt;br /&gt;
== Design Rationale ==&lt;br /&gt;
&lt;br /&gt;
The design separates class capability from class existence. Open-ended class resolution can expose models simply because the client supplies a class name. Explicit supported import and export class lists make behavior visible, testable, and safer.&lt;br /&gt;
&lt;br /&gt;
The system also avoids assuming every exportable thing should be importable. Grades are a reporting artifact, so they are export-only. Questionnaire packages are graph-shaped, so they use a package service. Assignment participants and teams need assignment context, so they override the generic table-shaped behavior.&lt;br /&gt;
&lt;br /&gt;
This produces a hybrid design:&lt;br /&gt;
&lt;br /&gt;
* Generic where the data is model-shaped and safe to process uniformly&lt;br /&gt;
* Specialized where the data is contextual, graph-shaped, computed, or safety-sensitive&lt;br /&gt;
&lt;br /&gt;
== Testing and Verification ==&lt;br /&gt;
&lt;br /&gt;
The implementation should be verified at three levels:&lt;br /&gt;
&lt;br /&gt;
* Model and helper specs for metadata, hidden fields, package export/import, and row-level import behavior&lt;br /&gt;
* Request specs for metadata endpoints, import endpoints, export endpoints, unsupported classes, and missing context&lt;br /&gt;
* Frontend tests or manual verification for modal metadata loading, preview, duplicate action selection, and generated downloads&lt;br /&gt;
&lt;br /&gt;
Important cases include:&lt;br /&gt;
&lt;br /&gt;
* Unsupported class names are rejected&lt;br /&gt;
* Missing assignment_id is rejected for assignment-scoped teams and participants&lt;br /&gt;
* Missing referenced records produce readable validation errors&lt;br /&gt;
* Duplicate rows use the selected duplicate strategy&lt;br /&gt;
* Questionnaire package preview reports intended actions before import&lt;br /&gt;
* Grade export is limited to the selected assignment and teaching-staff permissions&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-sequence.png&amp;diff=168102</id>
		<title>File:Import-sequence.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-sequence.png&amp;diff=168102"/>
		<updated>2026-04-27T02:26:08Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: Kdnadkar uploaded a new version of File:Import-sequence.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Export-sequence.png&amp;diff=168101</id>
		<title>File:Export-sequence.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Export-sequence.png&amp;diff=168101"/>
		<updated>2026-04-27T02:25:59Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: Kdnadkar uploaded a new version of File:Export-sequence.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-export-architecture.png&amp;diff=168100</id>
		<title>File:Import-export-architecture.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-export-architecture.png&amp;diff=168100"/>
		<updated>2026-04-27T02:25:46Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: Kdnadkar uploaded a new version of File:Import-export-architecture.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Export-sequence.png&amp;diff=168099</id>
		<title>File:Export-sequence.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Export-sequence.png&amp;diff=168099"/>
		<updated>2026-04-27T02:21:59Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-export-architecture.png&amp;diff=168098</id>
		<title>File:Import-export-architecture.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-export-architecture.png&amp;diff=168098"/>
		<updated>2026-04-27T02:21:48Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: Kdnadkar uploaded a new version of File:Import-export-architecture.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-sequence.png&amp;diff=168097</id>
		<title>File:Import-sequence.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-sequence.png&amp;diff=168097"/>
		<updated>2026-04-27T02:21:34Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-export-architecture.png&amp;diff=168096</id>
		<title>File:Import-export-architecture.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Import-export-architecture.png&amp;diff=168096"/>
		<updated>2026-04-27T02:13:50Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167933</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167933"/>
		<updated>2026-04-14T01:17:44Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Final Project Design Document */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project Design Document =&lt;br /&gt;
&lt;br /&gt;
For the final project work on E2606, we will be performing the following tasks:&lt;br /&gt;
* More comments to the code contributions made, as many methods added currently go unexplained as highlighted in the existing E2606 feedback&lt;br /&gt;
* An explicit whitelist of classes available for import/export added within the Wiki and mix-in comments&lt;br /&gt;
* Picture / Video Examples of importing and exporting each class type will be added&lt;br /&gt;
* Rework or Strengthen Questionnaires import/export to simplify the process and code readability&lt;br /&gt;
* Standardizing successes and failures in the import pipeline&lt;br /&gt;
* Revisit Team import/export documentation as it currently is focused on &amp;lt;Code&amp;gt;AssignmentTeam&amp;lt;/code&amp;gt; and &amp;lt;Code&amp;gt;MentoredTeam&amp;lt;/code&amp;gt; and NOT &amp;lt;Code&amp;gt;CourseTeam&amp;lt;/code&amp;gt;&lt;br /&gt;
* Creating a UML Diagram for the importable/exportable additions.&lt;br /&gt;
* Adding the ImportableExportable mixin to the Course and AssignmentParticipant classes.&lt;br /&gt;
&lt;br /&gt;
= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics utilized the same single-model flow as Users and is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;br /&gt;
&lt;br /&gt;
= Members =&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Mihir Kamat (mskamat@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167931</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167931"/>
		<updated>2026-04-14T01:02:27Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Final Project */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project Design Document =&lt;br /&gt;
&lt;br /&gt;
For the final project work on E2606, we will be performing the following tasks:&lt;br /&gt;
* More comments to the code contributions made, as many methods added currently go unexplained as highlighted in the existing E2606 feedback&lt;br /&gt;
* An explicit whitelist of classes available for import/export added within the Wiki and mix-in comments&lt;br /&gt;
* Picture / Video Examples of importing and exporting each class type will be added&lt;br /&gt;
* Rework or Strengthen Questionnaires import/export to simplify the process and code readability&lt;br /&gt;
* Standardizing successes and failures in the import pipeline&lt;br /&gt;
* Revisit Team import/export documentation as it currently is focused on &amp;lt;Code&amp;gt;AssignmentTeam&amp;lt;/code&amp;gt; and &amp;lt;Code&amp;gt;MentoredTeam&amp;lt;/code&amp;gt; and NOT &amp;lt;Code&amp;gt;CourseTeam&amp;lt;/code&amp;gt;&lt;br /&gt;
* Creating a UML Diagram for the importable/exportable additions.&lt;br /&gt;
&lt;br /&gt;
= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics utilized the same single-model flow as Users and is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;br /&gt;
&lt;br /&gt;
= Members =&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Mihir Kamat (mskamat@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167930</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167930"/>
		<updated>2026-04-14T00:52:55Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Final Project */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project =&lt;br /&gt;
&lt;br /&gt;
For the final project work on E2606, we will be performing the following tasks:&lt;br /&gt;
* More comments to the code contributions made, as many methods added currently go unexplained as highlighted in the existing E2606 feedback&lt;br /&gt;
* An explicit whitelist of classes available for import/export added within the Wiki and mix-in comments&lt;br /&gt;
* Picture / Video Examples of importing and exporting each class type will be added&lt;br /&gt;
* Rework or Strengthen Questionnaires import/export to simplify the process and code readability&lt;br /&gt;
* Standardizing successes and failures in the import pipeline&lt;br /&gt;
* Revisit Team import/export documentation as it currently is focused on &amp;lt;Code&amp;gt;AssignmentTeam&amp;lt;/code&amp;gt; and &amp;lt;Code&amp;gt;MentoredTeam&amp;lt;/code&amp;gt; and NOT &amp;lt;Code&amp;gt;CourseTeam&amp;lt;/code&amp;gt;&lt;br /&gt;
* Creating a UML Diagram for the importable/exportable additions.&lt;br /&gt;
&lt;br /&gt;
= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics utilized the same single-model flow as Users and is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;br /&gt;
&lt;br /&gt;
= Members =&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Mihir Kamat (mskamat@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167894</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167894"/>
		<updated>2026-04-13T23:34:37Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project =&lt;br /&gt;
&lt;br /&gt;
For the final project work on E2606, we will be performing the following tasks:&lt;br /&gt;
* More comments to the code contributions made, as many methods added currently go unexplained&lt;br /&gt;
* An explicit whitelist of classes available for import/export &lt;br /&gt;
* Picture / Video Examples of importing and exporting each class type will be added&lt;br /&gt;
* Rework or Strengthen Questionnaires import/export to simplify the process and code readability&lt;br /&gt;
* Standardizing successes and failures in the import pipeline&lt;br /&gt;
* Revisit Team import/export documentation as it currently is focused on &amp;lt;Code&amp;gt;AssignmentTeam&amp;lt;/code&amp;gt; and &amp;lt;Code&amp;gt;MentoredTeam&amp;lt;/code&amp;gt; and NOT &amp;lt;Code&amp;gt;CourseTeam&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics utilized the same single-model flow as Users and is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;br /&gt;
&lt;br /&gt;
= Members =&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Mihir Kamat (mskamat@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167893</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167893"/>
		<updated>2026-04-13T23:34:05Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project =&lt;br /&gt;
&lt;br /&gt;
For the final project work on E2606, we will be performing the following tasks:&lt;br /&gt;
* More comments to the code contributions made, as many methods added currently go unexplained&lt;br /&gt;
* An explicit whitelist of classes available for import/export &lt;br /&gt;
* Picture / Video Examples of importing and exporting each class type will be added&lt;br /&gt;
* Rework or Strengthen Questionnaires import/export to simplify the process and code readability&lt;br /&gt;
* Standardizing successes and failures in the import pipeline&lt;br /&gt;
* Revisit Team import/export documentation as it currently is focused on &amp;lt;Code&amp;gt;AssignmentTeam&amp;lt;\code&amp;gt; and &amp;lt;Code&amp;gt;MentoredTeam&amp;lt;\code&amp;gt; and NOT &amp;lt;Code&amp;gt;CourseTeam&amp;lt;\code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics utilized the same single-model flow as Users and is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;br /&gt;
&lt;br /&gt;
= Members =&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Mihir Kamat (mskamat@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167892</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167892"/>
		<updated>2026-04-13T23:33:44Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project =&lt;br /&gt;
&lt;br /&gt;
For the final project work on E2606, we will be performing the following tasks:&lt;br /&gt;
* More comments to the code contributions made, as many methods added currently go unexplained&lt;br /&gt;
* An explicit whitelist of classes available for import/export &lt;br /&gt;
* Picture / Video Examples of importing and exporting each class type will be added&lt;br /&gt;
* Rework or Strengthen Questionnaires import/export to simplify the process and code readability&lt;br /&gt;
* Standardizing successes and failures in the import pipeline&lt;br /&gt;
* Revisit Team import/export documentation as it currently is focused on &amp;lt;Code&amp;gt;AssignmentTeam&amp;lt;\Code&amp;gt; and &amp;lt;Code&amp;gt;MentoredTeam&amp;lt;\Code&amp;gt; and NOT &amp;lt;Code&amp;gt;CourseTeam&amp;lt;\Code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics utilized the same single-model flow as Users and is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;br /&gt;
&lt;br /&gt;
= Members =&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Mihir Kamat (mskamat@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167891</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167891"/>
		<updated>2026-04-13T23:32:27Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project =&lt;br /&gt;
&lt;br /&gt;
For the final project work on E2606, we will be performing the following tasks:&lt;br /&gt;
* More comments to the code contributions made, as many methods added currently go unexplained&lt;br /&gt;
* An explicit whitelist of classes available for import/export &lt;br /&gt;
* Picture / Video Examples of importing and exporting each class type will be added&lt;br /&gt;
* Rework or Strengthen Questionnaires import/export to simplify the process and code readability&lt;br /&gt;
* Standardizing successes and failures in the import pipeline&lt;br /&gt;
* Revisit Team import/export documentation as it currently is focused on &amp;lt;Code&amp;gt;AssignmentTeam&amp;lt;Code\&amp;gt; and &amp;lt;Code&amp;gt;MentoredTeam&amp;lt;Code\&amp;gt; and NOT &amp;lt;Code&amp;gt;CourseTeam&amp;lt;Code\&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics utilized the same single-model flow as Users and is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;br /&gt;
&lt;br /&gt;
= Members =&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Mihir Kamat (mskamat@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167890</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167890"/>
		<updated>2026-04-13T23:31:17Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Final Project =&lt;br /&gt;
&lt;br /&gt;
For the final project work on E2606, we will be performing the following tasks:&lt;br /&gt;
* More comments to the code contributions made, as many methods added currently go unexplained&lt;br /&gt;
* An explicit whitelist of classes available for import/export &lt;br /&gt;
* Picture / Video Examples of importing and exporting each class type will be added&lt;br /&gt;
* Rework or Strengthen Questionnaires import/export to simplify the process and code readability&lt;br /&gt;
* Standardizing successes and failures in the import pipeline&lt;br /&gt;
* Revisit Team import/export documentation as it currently is focused on (AssignmentTeam) and (MentoredTeam) and NOT (CourseTeam)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics utilized the same single-model flow as Users and is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;br /&gt;
&lt;br /&gt;
= Members =&lt;br /&gt;
&lt;br /&gt;
* Kiran Nadkarni (kdnadkar@ncsu.edu)&lt;br /&gt;
* Mihir Kamat (mskamat@ncsu.edu)&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167652</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167652"/>
		<updated>2026-03-31T02:23:22Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Approach */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics utilized the same single-model flow as Users and is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167650</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167650"/>
		<updated>2026-03-31T02:17:57Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Expansion of Existing Import/Export */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in given they would be single model exports. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167649</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167649"/>
		<updated>2026-03-31T02:16:41Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Approach */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in, not the graph-based system. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167648</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167648"/>
		<updated>2026-03-31T02:16:24Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Approach */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Importing Teams and Topics is covered in [[#Expansion of Existing Import/Export |Expansion of Existing Import/Export]]&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Importing/Exporting Teams &amp;amp; Topics required minimal changes to the existing mix-in.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in, not the graph-based system. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system handles both exporting single models and exporting sub-models that have &amp;lt;code&amp;gt; has_many&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; relations. This can be triggered by the &amp;quot;export related sub-models&amp;quot; switch on the frontend.&lt;br /&gt;
&lt;br /&gt;
The graph export section lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167644</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167644"/>
		<updated>2026-03-31T01:49:37Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Expansion of Existing Import/Export */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in, not the graph-based system. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
1. Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
2. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
3. Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167643</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167643"/>
		<updated>2026-03-31T01:49:05Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in, not the graph-based system. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To reach the above-mentioned Import/Export features from Home, the admin can navigate to:&lt;br /&gt;
&lt;br /&gt;
(1) Manage -&amp;gt; Users&lt;br /&gt;
&lt;br /&gt;
(2) Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Topics&lt;br /&gt;
&lt;br /&gt;
(3) Manage -&amp;gt; Assignments -&amp;gt; Edit an Assignment -&amp;gt; Etc. -&amp;gt; Create Teams&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Additional Changes = &lt;br /&gt;
&lt;br /&gt;
Some QoL updates were made within this project, to allow easier functional tests. This was with respect to user and team editing. When performing initial checkout, roles were not appearing within the front-end Users table, so this project addresses that by adjusting the user_serializer.rb wiring to allow role, parent, and institution to be correctly viewable.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167641</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167641"/>
		<updated>2026-03-31T01:32:35Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Expansion of Existing Import/Export */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in, not the graph-based system. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has Participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167640</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167640"/>
		<updated>2026-03-31T01:32:18Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: /* Expansion of Existing Import/Export */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in, not the graph-based system. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;Users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167639</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167639"/>
		<updated>2026-03-31T01:31:52Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in, not the graph-based system. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167638</id>
		<title>CSC/ECE 517 Spring 2026 - E2606. Finishing Import and Export helper module</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Spring_2026_-_E2606._Finishing_Import_and_Export_helper_module&amp;diff=167638"/>
		<updated>2026-03-31T01:30:58Z</updated>

		<summary type="html">&lt;p&gt;Kdnadkar: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Problem Statement =&lt;br /&gt;
&lt;br /&gt;
Project E2560 introduced a generic framework for import and export functionality in Expertiza. Before this, each entity such as users, teams, or questionnaires had its own separate import/export logic, even though they all performed similar tasks like reading CSV files, mapping fields, validating data, and handling duplicates. This led to repeated and tightly coupled code.&lt;br /&gt;
&lt;br /&gt;
E2560 solved this by creating a unified structure using a reusable mixin (ImportableExportable), a service layer (ImportExportManager), configurable field mappings, and strategy-based duplicate handling. Models define their required fields and duplicate rules, while the service layer handles the CSV processing in a consistent and reusable way.&lt;br /&gt;
&lt;br /&gt;
The current E2560 import system shows almost every field that exists in the backend. This means users see many technical or system-controlled fields, such as internal IDs, timestamps, and tokens, which they do not need to understand or import. Showing all these fields makes the screen confusing and harder to use. It also increases the risk of users selecting or mapping fields that should actually be managed only by the system. In addition, the duplicate handling logic is very general and does not clearly explain how duplicates are treated for different types of data. The system is also not easily extendable to support importing other entities like Teams or Topics.&lt;br /&gt;
&lt;br /&gt;
In the current project, the import feature should be redesigned to support multiple entity types in a clean and scalable way. For each entity, we should clearly define which fields are mandatory, which are optional, and which are system-managed. Only the necessary and user-editable fields should be displayed in the UI. Duplicate handling rules should be defined separately for each entity type, with a short and clear explanation for each rule. This will make the system easier to use, reduce confusion, improve data quality, and allow future expansion without major rework.&lt;br /&gt;
&lt;br /&gt;
You should write code to import users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
You should write code to export assignment grades, author-feedback grades, teammate-review grades, users, teams, topics, and questionnaires with their associated “advice.”&lt;br /&gt;
&lt;br /&gt;
= Previous Work =&lt;br /&gt;
&lt;br /&gt;
This project builds heavily on [[CSC/ECE 517 Fall 2025 - E2560. Framework for Import and Export]]. Please go through the page to have a better idea of the working of the system.&lt;br /&gt;
&lt;br /&gt;
= Approach =&lt;br /&gt;
&lt;br /&gt;
* Importing/Exporting Users existed beforehand.&lt;br /&gt;
* Exporting Questionnaires with their associated QuestionAdvices involves exporting a model along with its constituent models. We used the system described in the section [[#Graph-based Export System|Graph-based Export System]] on this page to achieve this.&lt;br /&gt;
* Exporting Grades involves exporting data that doesn't persist in the DB or is a combination of different models in the db. We used the approach described in the section [[#Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data|Model Spoofing for Exporting Non-Persistent/Calculated DB data]].&lt;br /&gt;
* Hiding fields from the user is covered in [[#Hidden Fields|Hidden Fields]].&lt;br /&gt;
&lt;br /&gt;
= Graph-based Export System =&lt;br /&gt;
&lt;br /&gt;
The previous version of the model export system only exported single models. The current graph export system lives in backend/app/helpers/export_helper.rb and works in two stages:&lt;br /&gt;
&lt;br /&gt;
1. Build a relationship graph from a root class&lt;br /&gt;
&lt;br /&gt;
2. Export one CSV payload per class discovered in that graph&lt;br /&gt;
&lt;br /&gt;
This separation allows the system to first understand the structure of model relationships before delegating the actual CSV generation to the existing export pipeline. It keeps concerns clean: graph construction is handled independently from data export.&lt;br /&gt;
&lt;br /&gt;
== Graph Building ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;export_has_many_graph(root_class)&amp;lt;/code&amp;gt; starts by calling &amp;lt;code&amp;gt;build_export_graph(root_class)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each graph node looks like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., has_many: [...] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For each class, &amp;lt;code&amp;gt;build_export_graph&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
1. records the class name so it can be referenced later in the export phase&lt;br /&gt;
&lt;br /&gt;
2. determines headers with &amp;lt;code&amp;gt;mandatory_headers_for&amp;lt;/code&amp;gt;, which defines the base set of fields for export&lt;br /&gt;
&lt;br /&gt;
3. follows direct &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; associations declared on the model&lt;br /&gt;
&lt;br /&gt;
4. also finds descendant models that &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt; the current class using &amp;lt;code&amp;gt;descendants_with_belongs_to_parent&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This means the graph is not limited to explicitly declared &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; relationships. It also captures implicit reverse relationships through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, ensuring broader coverage of related data.&lt;br /&gt;
&lt;br /&gt;
So even when a relationship is discovered through &amp;lt;code&amp;gt;belongs_to&amp;lt;/code&amp;gt;, it still gets stored under the &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; array in the graph. This creates a consistent structure where all outward relationships are treated uniformly, simplifying traversal and later processing.&lt;br /&gt;
&lt;br /&gt;
== Cycle Protection ==&lt;br /&gt;
&lt;br /&gt;
The graph builder uses a visited set to avoid infinite recursion.&lt;br /&gt;
&lt;br /&gt;
If a class is revisited, it returns a shortened node like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; { class_name: ..., headers: ..., cyclic_reference: true, has_many: [] } &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This keeps the graph traversal safe when models point back to each other. It prevents stack overflows and runaway processing while still indicating that a relationship exists. The &amp;lt;code&amp;gt;cyclic_reference&amp;lt;/code&amp;gt; flag acts as a signal that the traversal was intentionally stopped at that point.&lt;br /&gt;
&lt;br /&gt;
== Export Header Propagation ==&lt;br /&gt;
&lt;br /&gt;
Once the graph is built, &amp;lt;code&amp;gt;each_graph_node_for_export&amp;lt;/code&amp;gt; walks through it and computes the export headers for each class.&lt;br /&gt;
&lt;br /&gt;
It does this by:&lt;br /&gt;
&lt;br /&gt;
1. starting with the node’s own headers&lt;br /&gt;
&lt;br /&gt;
2. passing prefixed parent headers down to child nodes so relational context is preserved&lt;br /&gt;
&lt;br /&gt;
3. removing identifier-style fields like &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;*_id&amp;lt;/code&amp;gt; to avoid leaking internal keys and to keep the output cleaner&lt;br /&gt;
&lt;br /&gt;
Header prefixing is handled by &amp;lt;code&amp;gt;prefix_headers_with_class_name&amp;lt;/code&amp;gt;, ensuring that fields inherited from parent nodes remain distinguishable and do not collide with local fields. Identifier cleanup is handled by &amp;lt;code&amp;gt;remove_identifier_fields&amp;lt;/code&amp;gt;, which strips out fields that are primarily useful for database relations rather than for export consumers. This step effectively flattens relational context into a CSV-friendly structure.&lt;br /&gt;
&lt;br /&gt;
== CSV Export Generation ==&lt;br /&gt;
&lt;br /&gt;
After gathering headers for each class, &amp;lt;code&amp;gt;export_has_many_graph&amp;lt;/code&amp;gt; calls:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt; Export.perform(class_name.constantize, headers, graph_export: false) &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for every discovered class.&lt;br /&gt;
&lt;br /&gt;
That means graph export does not generate CSV directly itself. Instead, it reuses the normal export flow for each class separately. This avoids duplication of logic and ensures consistency with standard exports. Each class is processed independently, but with headers that have been enriched by the graph traversal. The final result is an array of export payloads, one per class.&lt;br /&gt;
&lt;br /&gt;
== Current Behavior in Practice ==&lt;br /&gt;
&lt;br /&gt;
The behavior is covered in backend/spec/helpers/export_helper_spec.rb. For example, starting from Questionnaire, the graph export reaches related classes such as:&lt;br /&gt;
&lt;br /&gt;
1. Item&lt;br /&gt;
&lt;br /&gt;
2. QuestionAdvice&lt;br /&gt;
&lt;br /&gt;
3. Answer&lt;br /&gt;
&lt;br /&gt;
The specs then verify that the returned CSV contents include real records for each of those classes.&lt;br /&gt;
&lt;br /&gt;
= Model Spoofing for Exporting Non-Persistent/Calculated/Custom DB data =&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;code&amp;gt;Export&amp;lt;/code&amp;gt; service, an additional redirection was added to the code that performs the exporting. Instead of going through &amp;lt;code&amp;gt;model.all&amp;lt;/code&amp;gt; and adding each row to the csv, the export code calls a &amp;lt;code&amp;gt;filter&amp;lt;/code&amp;gt; function which retrieves data from the model. By default it is the &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; function, but it can be set to any custom function inside the model that aggregates or transforms data across one or more tables and returns it in a structured, row-based format.&lt;br /&gt;
&lt;br /&gt;
This allows the export system to work not only with persisted database records, but also with computed, derived, or combined datasets that do not exist as a single table in the schema. This data can then be safely exported to the end-user using the same export pipeline, without requiring changes to the core CSV generation logic.&lt;br /&gt;
&lt;br /&gt;
The requirements for spoofing a model is to:&lt;br /&gt;
&lt;br /&gt;
1. Not inherit from &amp;lt;code&amp;gt;ActiveRecord&amp;lt;/code&amp;gt; (this model does not correspond to a real database table and should remain decoupled from persistence concerns).&lt;br /&gt;
&lt;br /&gt;
2. Have it be &amp;lt;code&amp;gt;ImportableExportable&amp;lt;/code&amp;gt; (so it conforms to the interface expected by the export system).&lt;br /&gt;
&lt;br /&gt;
3. Define its &amp;lt;code&amp;gt;self.column_names&amp;lt;/code&amp;gt; (We're essentially faking a model so these are the attributes that it would supposedly have).&lt;br /&gt;
&lt;br /&gt;
4. Define the row format as a struct (ensuring each returned row has a consistent shape and can be treated like a typical model instance).&lt;br /&gt;
&lt;br /&gt;
5. Define its &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; (so the export system knows which fields must always be included versus those that are conditional or supplementary).&lt;br /&gt;
&lt;br /&gt;
6. Define the method for aggregating the data as a static method (this method is responsible for collecting, joining, and shaping the data into exportable rows).&lt;br /&gt;
&lt;br /&gt;
7. Set &amp;lt;code&amp;gt;filter -&amp;gt; { method_name }&amp;lt;/code&amp;gt; (this tells the export system to use the custom aggregation method instead of &amp;lt;code&amp;gt;all&amp;lt;/code&amp;gt; when retrieving records).&lt;br /&gt;
&lt;br /&gt;
This approach effectively “spoofs” a model, allowing complex or computed datasets to be exported as if they were standard ActiveRecord-backed models.&lt;br /&gt;
&lt;br /&gt;
See the implementation of models/grades.rb as an example.&lt;br /&gt;
&lt;br /&gt;
= Hidden Fields =&lt;br /&gt;
&lt;br /&gt;
It was also mentioned in the project description that the end user has to be insulated from accessing certain fields, for less confusion, as well as for increased security. To achieve this, a new type of field apart from &amp;lt;code&amp;gt;mandatory_fields&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;optional_fields&amp;lt;/code&amp;gt; have been added, called &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt;. These fields define what is effectively completely hidden from the end user. &lt;br /&gt;
&lt;br /&gt;
As defined in the E2560 project methodology, when exporting or importing, the frontend first sends a request to the backend for the metadata to show which fields should be exported or not. This project completely insulates the end user from fields that need to be hidden by removing the &amp;lt;code&amp;gt;hidden_fields&amp;lt;/code&amp;gt; metadata completely from the mandatory and optional fields that are sent to the frontend.&lt;br /&gt;
&lt;br /&gt;
Hidden fields take precedence over mandatory fields. If a hidden field is found in mandatory fields, it will first and foremost be considered as a hidden field, and not a mandatory field.&lt;br /&gt;
&lt;br /&gt;
= Expansion of Existing Import/Export =&lt;br /&gt;
&lt;br /&gt;
The Teams and Topics models utilized an expansion of the existing import/export mix-in, not the graph-based system. That being said, Teams CSV handling was reworked to use a unique team name and be formatted keying off of participants. This specifically scopes the import/export functionality to the assignment, because all teams would be associated within an assignment. On the front-end, the buttons and import-export pop-up were changed to be nearly identical to the &amp;quot;users&amp;quot; interface. Wiring was also established between the back-end and front-end for teams as it looked like E2560 established front-end-only mock examples of teams without referencing any data in the db.&lt;br /&gt;
&lt;br /&gt;
Topics are more isolated, they are also associated with an assignment, but there are no joined models (Teams has participants for example). The main change for topics was naming and additional migration being run to support SignUpTopic being changed to ProjectTopic. Import/export behavior was added, as well as a serializer to support cleaner data for the front-end. Lastly for the front-end, the buttons/UI for import/export were changed to be similar to &amp;quot;Users&amp;quot; and &amp;quot;Teams&amp;quot;.&lt;/div&gt;</summary>
		<author><name>Kdnadkar</name></author>
	</entry>
</feed>