Files
project-work/worker-toolkit-stocks-in-the-future/repo/docs/map/processes/import-students.md

4.7 KiB

type, universe, status, consumes, produces
type universe status consumes produces
process live verified
../objects/org/classroom.md
../objects/identity/student.md
../objects/money/portfolio.md
../objects/org/classroom-enrollment.md

import-students

An admin uploads a CSV and gets students with generated passwords, portfolios, and enrollments.

Verified 2026-08-16 against commit 63732df.

Input → Movement → Output

An admin posts a CSV of classroom_id,username pairs. BulkStudentImportService walks the rows and hands each to ImportStudentService, which creates a student with a generated password. Each successful create cascades into a portfolio and a primary classroom-enrollment via Student's callbacks. The admin is redirected with per-line counts.

Why this shape

Skip is a success, not a failure. ImportStudentService::Result has three actions — created, skipped, failed — and a skip returns success?: true (app/services/import_student_service.rb:4,35-42). Duplicate usernames, blank usernames, and blank classroom IDs are all skips (:25-27), so re-uploading the same file is safe and reports zero new students rather than erroring.

Line numbers start at 2. with_index(2) accounts for the header row (app/services/bulk_student_import_service.rb:11), so reported numbers match what the admin sees in a spreadsheet.

Passwords are generated, never chosen. MemorablePasswordGenerator concatenates two Faker superhero names and a number, stripping spaces, hyphens, and apostrophes (app/services/memorable_password_generator.rb:8-19) — memorable enough for a middle-schooler to type. faker is therefore a production dependency (Gemfile:13), not a test one. The file carries its own TODO: more robust solution later (:3).

The whole import hangs on classroom_id being present, because Student#create_initial_enrollment only fires when it is (app/models/student.rb:10,82-86). A row without it is skipped outright, which is what keeps enrollment-less students out of the system by this path.

Steps

  1. Admin posts to POST /admin/students/import (config/routes.rb:63); Admin::StudentsController#import rejects a blank file (app/controllers/admin/students_controller.rb:117-118).
  2. BulkStudentImportService.import_from_csv reads with headers: true (app/services/bulk_student_import_service.rb:8,11).
  3. Rows missing either field are dropped before the service is called and produce no result at all (:15) — they are invisible in the summary counts.
  4. ImportStudentService.call strips whitespace, then skips on blank username, existing username, or blank classroom ID (app/services/import_student_service.rb:22-28).
  5. Student.new(username:, classroom_id:, password: MemorablePasswordGenerator.generate) and save (:38-46). ActiveRecord::InvalidForeignKey — a classroom ID that does not exist — is rescued into a failed result (:50-52).
  6. Saving triggers Student callbacks: ensure_portfolio and create_initial_enrollment (app/models/student.rb:9-10).
  7. Results are wrapped with line numbers (:22) and partitioned for the flash message (app/controllers/admin/students_controller.rb:182,199).
  8. GET /admin/students/template downloads a sample CSV (app/services/bulk_student_import_service.rb:28-36).

Malformed CSV is caught at the controller and reported (app/controllers/admin/students_controller.rb:123).

If you change this

  • Hits: student creation and both of its callbacks; portfolio and classroom-enrollment, created as a side effect; Admin::StudentsController.
  • Does not hit: grade-book or grade-entry. Importing students does not create gradebook entries for them — gradebooks are created per classroom, and nothing backfills entries for students who arrive afterwards.

Notes

  • There is no transaction around the batch. A CSV that fails halfway leaves the earlier students created.
  • The import is row-at-a-time with a Student.exists? query per row; large files are slow but bounded.

Surfaces

Surface Role
Admin::StudentsController#import / #template admin writes
BulkStudentImportService, ImportStudentService create
MemorablePasswordGenerator generates credentials

See