-
Notifications
You must be signed in to change notification settings - Fork 5
Make NCES ID mandatory for US schools #638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Make NCES ID mandatory for US schools #638
Conversation
8994994 to
27313f4
Compare
…number-validation-error-message-and-form-state-api
| rescue StandardError => e | ||
| Sentry.capture_exception(e) | ||
| response[:error] = response[:school].errors | ||
| response[:error_types] = response[:school].errors.details |
| rescue StandardError => e | ||
| Sentry.capture_exception(e) | ||
| response[:error] = response[:school].errors | ||
| response[:error_types] = response[:school].errors.details |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR aims to make URN (reference) mandatory for UK schools and NCES ID (district_nces_id) mandatory for US schools, with format validation and scoped uniqueness constraints. However, there's a critical bug: the reference field is missing presence: true validation in the model, meaning URN is not actually mandatory for UK schools despite the PR's stated purpose.
Key changes:
- Converted database indexes for
referenceanddistrict_nces_idto partial unique indexes allowing rejected schools to release identifiers for reuse - Added format validation for URN (5-6 digits) and NCES ID (12 digits) fields
- Added
district_nameas required field for US schools - Enhanced error responses to include detailed error types for better API feedback
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| app/models/school.rb | Adds conditional format and uniqueness validations for reference/district_nces_id, plus united_kingdom?/united_states? helper methods. Missing presence validation for reference. |
| db/migrate/20251208134354_change_reference_and_nces_id_indexes_to_partial.rb | Converts reference and district_nces_id indexes to partial unique indexes (WHERE rejected_at IS NULL) |
| db/schema.rb | Reflects updated partial index constraints |
| spec/models/school_spec.rb | Adds comprehensive validation tests, but test on line 143-147 incorrectly asserts reference is not required for UK schools |
| spec/factories/school.rb | Adds sequential reference generation for default GB schools |
| spec/features/school/creating_a_school_spec.rb | Updates school creation to include reference field |
| spec/features/admin/schools_spec.rb | Sets reference to nil when testing US school scenarios |
| spec/concepts/school/create_spec.rb | Updates school creation params to include reference |
| spec/jobs/school_import_job_spec.rb | Adds district_name and district_nces_id to US school test data |
| lib/tasks/seeds_helper.rb | Adds conditional population of country-specific identifier fields, but missing district_name for US schools |
| lib/concepts/school/operations/create.rb | Adds error_types to operation response for detailed error handling |
| app/jobs/school_import_job.rb | Passes through district_name, district_nces_id, and school_roll_number fields |
| app/controllers/api/schools_controller.rb | Returns error_types in addition to error messages for create endpoint |
| config/locales/en.yml | Adds custom error messages for reference, district_nces_id, and school_roll_number validations |
| Gemfile.lock | Adds arm64-darwin-25 platform (developer environment update) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.
| school: | ||
| website: "must be a valid URL" | ||
| reference: "must be 5-6 digits (e.g., 100000)" | ||
| reference_urn_exists: "URN number already exists" |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom uniqueness error message "URN number already exists" is inconsistent with the standard Rails error messages used elsewhere. According to the PR description's API Error Responses table, the uniqueness error should be "has already been taken" to match Rails conventions.
Additionally, using "URN number" is redundant since URN already stands for "Unique Reference Number". Consider using either "URN has already been taken" or "reference has already been taken" for consistency.
| reference_urn_exists: "URN number already exists" | |
| reference_urn_exists: "URN has already been taken" |
| district_nces_id: "must be 12 digits (e.g., 010000000001)" | ||
| district_nces_id_exists: "NCES ID already exists" | ||
| school_roll_number: "must be numbers followed by letters (e.g., 01572D)" | ||
| school_roll_number_exists: "School roll number already exists" |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom uniqueness error message "School roll number already exists" is inconsistent with the standard Rails error messages. According to the PR description's API Error Responses table and Rails conventions, the uniqueness error should be "has already been taken".
Consider changing this to "has already been taken" for consistency with Rails standards.
| school_roll_number_exists: "School roll number already exists" | |
| school_roll_number_exists: "has already been taken" |
| format: { with: /\A\d{5,6}\z/, allow_nil: true, message: I18n.t('validations.school.reference') }, | ||
| if: :united_kingdom? | ||
| validates :district_nces_id, | ||
| uniqueness: { conditions: -> { where(rejected_at: nil) }, case_sensitive: false, allow_blank: true, message: I18n.t('validations.school.district_nces_id_exists') }, |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format validation uses allow_nil: true, but the uniqueness validation uses allow_blank: true. This creates an inconsistency where an empty string would pass uniqueness validation but fail format validation.
Since there's a normalize_district_fields callback (lines 114-117) that converts blank values to nil, both validations should use allow_nil: true for consistency, or the format validation should also use allow_blank: true.
| uniqueness: { conditions: -> { where(rejected_at: nil) }, case_sensitive: false, allow_blank: true, message: I18n.t('validations.school.district_nces_id_exists') }, | |
| uniqueness: { conditions: -> { where(rejected_at: nil) }, case_sensitive: false, allow_nil: true, message: I18n.t('validations.school.district_nces_id_exists') }, |
| it 'returns error if school_roll_number is not unique' do | ||
| duplicate_school = build(:school, school_roll_number: '01572D', country_code: 'IE') | ||
| duplicate_school.valid? | ||
| expect(duplicate_school.errors.details[:school_roll_number]).to include(hash_including(error: :taken)) |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message assertion expects the :taken error symbol, but the model validation on line 30 of school.rb specifies a custom message I18n.t('validations.school.school_roll_number_exists') which translates to "School roll number already exists" according to config/locales/en.yml line 23.
The test should expect the custom error message instead of checking for the :taken error symbol to accurately reflect the actual validation behavior.
| expect(duplicate_school.errors.details[:school_roll_number]).to include(hash_including(error: :taken)) | |
| expect(duplicate_school.errors[:school_roll_number]).to include(I18n.t('validations.school.school_roll_number_exists')) |
| reference: "must be 5-6 digits (e.g., 100000)" | ||
| reference_urn_exists: "URN number already exists" | ||
| district_nces_id: "must be 12 digits (e.g., 010000000001)" | ||
| district_nces_id_exists: "NCES ID already exists" |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom uniqueness error message "NCES ID already exists" is inconsistent with the standard Rails error messages. According to the PR description's API Error Responses table, the uniqueness error should be "has already been taken" to match Rails conventions.
Consider changing this to "has already been taken" for consistency with Rails standards and the documented API response format.
| district_nces_id_exists: "NCES ID already exists" | |
| district_nces_id_exists: "has already been taken" |
|
|
||
| duplicate_school = build(:school, reference: '100000') | ||
| duplicate_school.valid? | ||
| expect(duplicate_school.errors.details[:reference]).to include(hash_including(error: :taken)) |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message assertion expects the Rails default message "has already been taken", but the model validation on line 20 of school.rb specifies a custom message I18n.t('validations.school.reference_urn_exists') which translates to "URN number already exists" according to config/locales/en.yml line 19.
The test should expect the custom error message instead of the default Rails message to accurately reflect the actual validation behavior.
| expect(duplicate_school.errors.details[:reference]).to include(hash_including(error: :taken)) | |
| expect(duplicate_school.errors[:reference]).to include(I18n.t('validations.school.reference_urn_exists')) |
| it 'returns error if district_nces_id is not unique' do | ||
| duplicate_school = build(:school, country_code: 'US', district_nces_id: '010000000001') | ||
| duplicate_school.valid? | ||
| expect(duplicate_school.errors.details[:district_nces_id]).to include(hash_including(error: :taken)) |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message assertion expects the Rails default message "has already been taken" via the :taken symbol, but the model validation on line 24 of school.rb specifies a custom message I18n.t('validations.school.district_nces_id_exists') which translates to "NCES ID already exists" according to config/locales/en.yml line 21.
The test should expect the custom error message instead of checking for the :taken error symbol to accurately reflect the actual validation behavior.
| expect(duplicate_school.errors.details[:district_nces_id]).to include(hash_including(error: :taken)) | |
| expect(duplicate_school.errors[:district_nces_id]).to include(I18n.t('validations.school.district_nces_id_exists')) |
Make NCES ID mandatory for US schools
Add conditional presence validation for US NCES ID (district_nces_id) fields, with format validation and scoped uniqueness to allow reuse after school rejection. This improves validation error handling so users receive specific, actionable error messages instead of generic form errors.
Status
Closes: https://github.com/orgs/RaspberryPiFoundation/projects/51/views/11?pane=issue&itemId=120513360&issue=RaspberryPiFoundation%7Cdigital-editor-issues%7C780
Related to: (standalone url)
Points for consideration:
Security
What's changed?
Database:
referenceindex to partial unique index (WHERE rejected_at IS NULL)district_nces_idindex to partial unique index (WHERE rejected_at IS NULL)Model (School):
referencerequired for UK (country_code == 'GB')district_nces_idrequired for US (country_code == 'US')united_kingdom?andunited_states?helper methodsAPI Error Responses:
reference"can't be blank"reference"has already been taken"reference"must be 5-6 digits (e.g., 100000)"district_nces_id"can't be blank"district_nces_id"has already been taken"district_nces_id"must be 12 digits (e.g., 010000000001)"Tests:
referencefor default GB schools