4.7 KiB
type, universe, status, consumes, produces
| type | universe | status | consumes | produces | ||||
|---|---|---|---|---|---|---|---|---|
| process | live | verified |
|
|
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
- Admin posts to
POST /admin/students/import(config/routes.rb:63);Admin::StudentsController#importrejects a blank file (app/controllers/admin/students_controller.rb:117-118). BulkStudentImportService.import_from_csvreads withheaders: true(app/services/bulk_student_import_service.rb:8,11).- 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. ImportStudentService.callstrips whitespace, then skips on blank username, existing username, or blank classroom ID (app/services/import_student_service.rb:22-28).Student.new(username:, classroom_id:, password: MemorablePasswordGenerator.generate)and save (:38-46).ActiveRecord::InvalidForeignKey— a classroom ID that does not exist — is rescued into afailedresult (:50-52).- Saving triggers
Studentcallbacks:ensure_portfolioandcreate_initial_enrollment(app/models/student.rb:9-10). - Results are wrapped with line numbers (
:22) and partitioned for the flash message (app/controllers/admin/students_controller.rb:182,199). GET /admin/students/templatedownloads 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
- Objects: student, classroom-enrollment
- Source:
app/services/bulk_student_import_service.rb,app/services/import_student_service.rb