Skip to content

Instantly share code, notes, and snippets.

@ckozus
Created April 3, 2026 15:40
Show Gist options
  • Select an option

  • Save ckozus/0f1d08d642b4335de5cb16688aca94bc to your computer and use it in GitHub Desktop.

Select an option

Save ckozus/0f1d08d642b4335de5cb16688aca94bc to your computer and use it in GitHub Desktop.
Managed Students - Impact Analysis & Implementation Plan

Managed Students - Impact Analysis & Implementation Plan

Context

Colleges need to create and manage students without those students having user accounts. The goal:

  1. Students without users - structural changes to support student.user being nil
  2. College-managed registrations - colleges create student_de_courses (triggering active_flows) and college_student_applications for these students
  3. Platform stability - all existing features must work with userless students

Key constraint: Email is optional for managed students. Colleges may not have email at creation time. This means student.email can return nil - not just "read from a different source" but genuinely absent.

Gated by a per-college local option. Creation UI/flows are a future phase.


1. Infrastructure: Students Without Users

Core Model Changes (app/models/student.rb)

Line Current Change
3 belongs_to :user, autosave: true Add optional: true
50-54 before_create assumes user present Wrap in if self.user.present?
60 delegate(:cell_phone, :cell_phone=, :email, :email=, :login, to: :user) Replace with safe accessors
810 self.user.branded_host self.user&.branded_host

Safe Accessor Pattern (replaces delegate on line 60):

def email
  user&.email || read_attribute(:email)
end

def cell_phone
  user&.cell_phone || read_attribute(:cell_phone)
end

def login
  user&.login
end

These return nil when there's no user AND no value on the student record. Callers must handle nil. The existing email= (line 64) already guards with return unless user.present?.

Delegation chain effect: CollegeStudentApplication (line 54) delegates :email, :cell_phone to :student. After this change, csa.emailstudent.email → safe accessor → may return nil. This is correct behavior.

Database Migration

  • managed boolean on students (default: false, null: false) + index
  • cell_phone string on students (currently only on users)
  • College local option (e.g., MANAGED_STUDENTS_ENABLED)

What Happens Without a User AND Without Email

Feature Behavior
student.email Returns nil
student.cell_phone Returns nil
student.login Returns nil
Notifications to student Not sent (no user, no email)
SIS integrations (EthosApi etc.) Email omitted from payload (already checks .present?)
Reports email columns Blank/NULL
Liquid templates {{ student.email }} Renders empty string
Workflow steps accessing email Must handle nil (most already do)

2. College-Managed Registrations

Existing Infrastructure Already Supports This

  1. CreateCourseRegistrationForm (app/forms/create_course_registration_form.rb) - validates student_id and course_section_id, does NOT require user. But requires student.college_application_for_college_or_system_present?(college) — student MUST have a completed CollegeStudentApplication.

  2. CreateStudentDeCourseRegistrationService — creates StudentDeCourse, triggers BackgroundRegisterCourse which launches ActiveFlowDefinition.instantiate_and_launch_active_flow. No user dependency.

  3. CollegeStudentApplication — stores all demographic/contact data. Stores student_number (primary SIS identifier). Required for registration. Does NOT reference User directly.

SIS Integrations Are Safe

All SIS integrations use student_number from CSA, not student.user:

Integration Files Accesses email? Handles nil email?
EthosApi 37 Yes, via student.email Yes - checks .present? before adding to payload
Banner 17 No N/A
Colleague 6 No N/A
Jenzabar 3 No N/A
PeopleSoft 1 No N/A
IvyTech (custom) 3 Yes, student.user.email No - needs safe accessor
CSUB (custom) 1 Yes, student.user.email No - crashes, needs fix

Implication: CSA Required for Registrations

Managed students must have a CollegeStudentApplication to be registered. This is the natural data container — it stores student_number, SSN, demographics, all data that SIS integrations need.


3. Full Impact Inventory

3a. Crash Points (Will Error Without Fix)

HIGH - Direct crashes with nil user or nil email:

File Line Code Issue
app/models/student.rb 60 delegate(:email, :cell_phone, :login, to: :user) Crashes if user nil
app/models/student.rb 50-54 self.user.email in before_create Crashes if user nil
app/models/student.rb 810 self.user.branded_host Crashes if user nil
app/models/student.rb 344 first_approver.user.email in get_approver Crashes if approver user nil
app/mailers/active_flow_step_mailer.rb 190 @target_object.student.user appended to array Appends nil, downstream methods fail
app/mailers/active_flow_step_mailer.rb 396 sent_addresses << user.email Adds nil to array without checking .present?
app/mailers/students_reminder_mailer.rb 29 user = student.user then calls methods Crashes
app/mailers/college_student_application_mailer.rb 7 user = @student.user Crashes
app/mailers/survey_mailer.rb 34 @user = survey.student.user Crashes
app/jobs/background_student_batch_action.rb 13 student.user.destroy Crashes if user nil
app/models/csub_application_api.rb 66 student.user.email Crashes if user nil

MEDIUM - Could produce unexpected behavior with nil email:

File Line Code Issue
app/models/reports/college/all_student_workflows_report.rb 66 student.user.email Crashes (after safe accessor: returns nil, report shows blank)
Workflow init steps (SAC, DBU, etc.) various fields['approver_email'] = student.get_approver_email(college) Stores nil in fields, passed to mailers
app/models/form.rb 52,134,217 liquid_params['user'] = student.user Liquid renders empty for nil — cosmetic only

3b. Already Safe (No Changes Needed)

Area Why Safe
EthosApi person matching Checks student.email.present? before adding to payload
Parent/approver email getters (first_parent_email, first_approver_email) Use &. safe navigation
get_parent_email, get_parent_info Check .present? before returning
Denormalized tables (registration_active_flows, report_*) LEFT OUTER JOINs produce NULLs
Most reports Use denormalized columns (NULLs are valid)
Mailer all_email_addresses cleanup Line 361: .flatten.compact.reject(&:blank?) removes nils
admission_application_hash Uses self.user&.email
ability.rb permissions Rules naturally don't match for nil user
student_de_course.rb:get_target_user Dead code — not called anywhere meaningful
Admin pages rec.user.login rescue '' Rescue handles nil

3c. Notifications / Mailers

Mailer Fix
active_flow_step_mailer.rb (line 190) Guard: only add student.user if present. Also fix line 396: check user.email.present? before pushing.
students_reminder_mailer.rb (line 29) Early return if student.user.blank?. Also scope reminder queries to exclude managed students.
college_student_application_mailer.rb Early return if user blank. (Managed students won't complete apps, so this won't be triggered, but guard anyway.)
survey_mailer.rb Early return if user blank.

Notification principle: For managed students, the "Student" notification role is always empty. HS and College role notifications still work (they use their own user records).

3d. Student Accounts Page ("All Students")

Feature Impact Action Needed
Student listing Works (student.user&. already used in some places) Minor guards
Status column Managed students fall into "account_not_yet_confirmed" (misleading) Add "Managed" status
Send Reminders batch action Must skip managed students Filter out or skip gracefully
Batch Delete Calls user.destroy — crashes if nil Guard: destroy student directly if no user
Individual Delete Same as batch Same guard
Reminder email templates Reference <student-login> Not sent for managed students (mailer guard)

3e. Admin Pages

Page Impact
Admin Students Index rec.user.login rescue ''Safe
Admin Students Show row :user shows nil — Acceptable
Admin CSA No direct student.user refs — Safe
Admin "View As" Redirects to student portal — Needs guard for managed students
Admin "Create Registration" Works if CSA exists

3f. Reports

Report Email Source Impact
AllStudentWorkflowsReport student.user.email direct Crashes — use safe accessor
CourseSectionRosterReport Denormalized column Safe (NULL)
RegistrationsAbbreviatedReport Denormalized columns Safe (NULL)
StudentApplicationStatusReport Denormalized user_email Safe (NULL)
HelperMethods#student_account_status student.user.nil? Already handles nil but returns misleading status
BoisestateOnCampusApplicationsReport Eager loads :user Needs audit

Implication: Email columns in reports will be blank for managed students. This is the expected behavior since email is genuinely optional.

3g. Workflow Steps

College-specific steps are low risk (managed students are opt-in per college). But safe navigation is good practice:

Step Change
steps/ivytech/* (2 files) student.user.emailstudent.email
steps/boisestate/* (1 file) student.user.cell_phonestudent.cell_phone
steps/niacc/* (1 file) student.user.cell_phonestudent.cell_phone

3h. Views / Templates

View Protection
6 reminder email templates Mailer guard prevents rendering; add &. as defense-in-depth
shared/form_parts/_student_signature*.html.erb (3 files) Add &. on user.scanned_signature
college_student_applications/_name_and_address_group_ajax_validations.html.erb Add &. on student.user.id
_college_student_applications_page_side.html.erb Already guarded with @student.user &&

3i. Controllers

Controller Change
students/students_controller.rb before_action guard on account pages (edit_user_account, etc.)
student_accounts_controller.rb Guard destroy: if no user, destroy student directly

3j. Background Jobs

Job Change
background_student_batch_action.rb Guard student.user before destroy; destroy student directly if no user
background_send_student_reminders.rb Filter managed students from query; mailer guard is backup

4. Questions for Product

Critical

  1. CSA for managed students: Managed students MUST have a CSA for registrations (enforced by CreateCourseRegistrationForm). Should CSA be created with the student, or separately? What fields are mandatory? (student_number? SSN? address?)

  2. Status on All Students page: Managed students currently show "Account Not Yet Confirmed" (misleading). Add new status "Managed (No Account)"? Or should managed students appear on a separate listing entirely?

  3. Batch operations: When "Send Reminders" or "Delete" is used, should managed students be silently skipped, filtered out of selection, or shown with a warning?

  4. Reports with blank email: Reports will show blank email/login for managed students. Is this acceptable? Should we pull email from a different source, or add an indicator?

  5. SIS student_number: For SIS-integrated colleges, managed students need student_number. Will colleges assign this manually, or should the system create the student in the SIS and get back a number?

  6. Parent/Approver workflows: Will managed students have parent consent workflows? Parents are StudentAnonymousParticipant records with their own User accounts. If managed students need parent consent, do parents still need accounts?

  7. Future upgrade path: Can a managed student later get a full User account? Does the system need to support this transition?

Lower Priority

  1. CSA visualization: Will admins view/edit CSA data for managed students through existing admin pages, or through a new interface?

  2. Scanned signatures: Stored on User record. Will managed students need signatures? If so, where stored?

  3. Student merge: If a managed student is merged with a non-managed student, which record wins?

  4. "View As" admin feature: Hide the button for managed students, or show explanatory message?


5. Testing / QA Plan

Setup: Test Data Creation

Create via console or scripts (no creation UI in this phase):

  • Managed student (managed: true, no user, email may or may not be present)
  • CollegeStudentApplication for the managed student (with student_number, demographics)
  • StudentDeCourse registration (triggers active_flow)
  • Compare side-by-side with a normal student through all scenarios

Category A: Admin Pages

# Test Expected
A1 Admin Students Index: managed student in list Listed, login column blank
A2 Admin Students Show: view managed student All panels render, user row shows blank/nil
A3 Admin Students Show: sidebars (CSA, registrations, terms) Load without error
A4 Admin Students Show: "View As" button Hidden or shows message
A5 Admin Students Show: "Create Registration" Works if CSA exists
A6 Admin CSA Index: managed student's CSA Listed normally
A7 Admin CSA Show: all panels No errors
A8 Admin Registration Active Flows Listed, user fields NULL

Category B: Student Accounts Page (All Students)

# Test Expected
B1 Managed student in listing Shown with correct status
B2 Status column Shows "Managed" or similar, NOT "Account Not Yet Confirmed"
B3 Filter by status Managed status filter works
B4 Filter by name / high school Works
B5 Send Reminders with managed students selected Skipped gracefully, no error
B6 Batch Delete with managed students Student destroyed directly, no crash
B7 Individual Delete for managed student Same as B6

Category C: Reports

# Test Expected
C1 AllStudentWorkflowsReport Email blank, no crash
C2 CourseSectionRosterReport Email blank (denormalized NULL)
C3 RegistrationsAbbreviatedReport Email/Login blank
C4 StudentApplicationStatusReport user_email blank
C5 CSV export of each report Exports without error
C6 HelperMethods#student_account_status Correct status for managed students

Category D: Workflows / Active Flows

# Test Expected
D1 Create registration for managed student (console) StudentDeCourse created, active_flow launched
D2 Workflow steps execute No crashes
D3 Notification to "Student" role Skipped (no user/email)
D4 Notification to "College" role Sent normally
D5 Notification to "HS" role Sent normally
D6 Form-generating steps Forms render (_student_signature handles nil)
D7 SIS integration steps (if applicable) Use student_number from CSA
D8 Steps that set approver_email/parent_email fields Handle nil gracefully

Category E: Mailers

# Test Expected
E1 ActiveFlowStepMailer for managed student Student role skipped, others notified
E2 StudentsReminderMailer for managed student Early return, no email, no crash
E3 CollegeStudentApplicationMailer Not triggered (managed students don't complete apps)
E4 SurveyMailer for managed student Early return, no crash

Category F: Denormalization

# Test Expected
F1 Denormalize managed student's registration User columns NULL, email columns NULL
F2 Denormalize managed student's CSA user_email, user_cell_phone NULL
F3 Denormalization validation (admin) No false mismatches

Category G: Edge Cases

# Test Expected
G1 Managed student with email on student record Email shows in reports/APIs that check it
G2 Managed student without email All blank, no crashes
G3 Managed student with CSA but no student_number SIS steps fail gracefully (existing behavior)
G4 Normal student (non-managed) full flow No regression - works exactly as before
G5 Student merge: managed + non-managed TBD (product question)
G6 Upgrade managed student (add user later) TBD (product question)

6. Implementation Phases (When Ready)

Phase Description Risk
0 DB migration: managed, cell_phone columns + local option Low
1 Student model: optional user, safe accessors, guarded callbacks Medium
2 Mailer guards (4 mailers + get_addresses_from_users nil check) Low
3 Student Accounts page: status, batch actions, delete guards Low
4 Workflow steps: use safe accessors Low
5 Background jobs: nil guards Low
6 Reports: safe accessors, managed status Low
7 Views/templates: safe navigation Low
8 Controllers: before_action guards Low
9 Admin pages: managed indicator, guard "View As" Low
10 Other models: form.rb, csub_application_api.rb Low
Future Creation UI, CSA creation flow, batch import Medium

Phase 1 is the foundation. Once deployed, the system won't crash on nil users. All other phases can be done incrementally.


7. Key Files

Must change:

  • app/models/student.rb — optional user, safe accessors, guarded callbacks
  • app/mailers/active_flow_step_mailer.rb — notification guard + nil email guard (line 396)
  • app/mailers/students_reminder_mailer.rb — early return guard
  • app/mailers/college_student_application_mailer.rb — early return guard
  • app/mailers/survey_mailer.rb — early return guard
  • app/controllers/student_accounts_controller.rb — delete guard
  • app/jobs/background_student_batch_action.rb — batch delete guard
  • app/models/reports/college/helper_methods.rb — managed status
  • app/models/reports/college/all_student_workflows_report.rb — safe accessor
  • app/models/csub_application_api.rbstudent.user&.email || ''

Should change (safety):

  • app/views/shared/form_parts/_student_signature*.html.erb (3 files)
  • app/views/college_student_applications/_name_and_address_group_ajax_validations.html.erb
  • app/models/steps/ivytech/ (2), steps/boisestate/ (1), steps/niacc/ (1)
  • app/models/form.rb — conditional liquid user param
  • app/controllers/students/students_controller.rb — account page guards
  • app/models/student.rb:344get_approver nil guard

No changes needed:

  • Denormalization infrastructure (LEFT OUTER JOINs → NULLs)
  • SIS integration logic (EthosApi, Banner, Colleague etc. — use student_number)
  • Most reports (use denormalized columns)
  • ability.rb (rules naturally don't match nil user)
  • student_de_course.rb:get_target_user (dead code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment