Store school email domains#787
Conversation
There was a problem hiding this comment.
Pull request overview
Introduces first-class storage for school email domains by adding a dedicated table/model and wiring it into the School domain model, with accompanying specs.
Changes:
- Added
school_email_domainstable with a composite unique index per school. - Added
SchoolEmailDomainmodel withbefore_validationnormalization (strip leading@, downcase). - Updated
Schooltohas_many :school_email_domainsand added/updated model specs.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/models/school_spec.rb | Adds association + dependent-destroy coverage for school_email_domains. |
| spec/models/school_email_domain_spec.rb | Adds model spec coverage for domain normalization behavior. |
| db/schema.rb | Reflects new school_email_domains table and foreign key. |
| db/migrate/20260420104937_create_school_email_domains.rb | Creates the new school_email_domains table and composite unique index. |
| app/models/school_email_domain.rb | Introduces the new model and domain normalization callback. |
| app/models/school.rb | Adds has_many :school_email_domains, dependent: :destroy. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
4740867 to
aff003a
Compare
Test coverage91.13% line coverage reported by SimpleCov. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
fspeirs
left a comment
There was a problem hiding this comment.
Overall this looks like the right approach. I have one consideration about validating domains - I feel like it would be helpful to be stricter in checking that the user hasn't typo'ed their domain or misunderstood what we are asking for.
fspeirs
left a comment
There was a problem hiding this comment.
Directionally, quite correct. Just some sharpening of the normalisation approach in school_email_domain and a request for a testing method in school.rb. The lack of a testing method is non-blocking if it's coming in a separate PR.
|
The regex we use in profile side to validate is While it is similar, we would reject things that had a dash in the wrong place, like |
4fda047 to
ead00de
Compare
ead00de to
81f8ad5
Compare
00a63d5 to
e5a847a
Compare
e5a847a to
f5eb365
Compare
c5e1590 to
62a5acb
Compare
62a5acb to
1e8b16a
Compare
Add a migration to create the school_email_domains table, storing a domain string against a school_id
Create the model and add initial specs
Removing leading @ symbols and lower case domains
Extract domain validation from school_email_domain model to module Call it from SchoolEmailDomain and School
1e8b16a to
b789663
Compare
This PR implements the back-end for class join codes — students join a class by entering an 8-character code in the format `CDDD-CDDD` (consonant + three digits, twice, separated by a hyphen). ## Core Logic The core logic for this work lives in `JoinController#create`, which determines what response the front-end gets based on the user who is trying to join. This method returns a `show.json.builder` with values that allow the front-end to determine what to show the user, or where to redirect them to based on their status. The status value is computed in `action_status`. Another feature of `JoinController#show` is that it may be called by unauthenticated users, so that we can destructure the join code into a school and class in order to show the user a school and class name before they're redirected to log in. There are modifications to `School` to allow that model to carry a join code and regenerate it. Conceptually, a join code is a globally unique code that represents both a class code and a school code. The other component of interest is the `JoinCodeGenerator` which produces the codes in the first place. ## Changes - Add `join_code` column with a unique index to `school_classes`, plus a backfill migration for existing classes. - Add `JoinCodeGenerator` with `.generate` and `.normalize` (canonicalises user input to the hyphenated form for DB lookup). - Auto-assign join codes on `SchoolClass` create/import with a uniqueness retry, plus a `regenerate_join_code!` helper. - Add `GET /api/join/:join_code` returning the prospective user's status. The full set of statuses is: - `unauthenticated` — no current user - `joinable` — the user can be enrolled as a student - `joinable_as_teacher` — the user already has a teacher role for this school and will be added to the class as a teacher - `owner` — the user owns this school; treated as already a member for redirect purposes - `already_member` — the user is already a member of this class - `wrong_school` — the user has a role in a different school - `domain_mismatch` — the user's email domain is not registered for this school - `not_a_student` — the user has a non-student role elsewhere - Add `POST /api/join/:join_code` to enrol the current user as a student (or teacher, if `joinable_as_teacher`) of the school and class. - Add `POST /api/schools/:school_id/classes/:id/regenerate_join_code` (nested under the existing `school_classes` resource). - Expose `join_code` on the school class JSON - Add `School#valid_email?` (built on #787's domain validation) for the `domain_mismatch` check. - Add `School#auto_join_enabled?` to allow the front end to prompt users correctly about the requirements for auto-join. - Update CanCan abilities so school owners and class teachers can `regenerate_join_code`. - Add specs covering the model, generator, controllers, and abilities. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Status
Closes #1325
What's changed?
school_email_domainstableSchoolEmailDomainmodelSchoolEmailDomainValidatorthat normalises and checks domainsSchoolvalid_domain?method onSchool