Conversation
PUT /api/schools/:school_id/ownership_transfer/accept marks the school's pending transfer as completed. Only the nominee can accept - neither the requester nor anyone else at the school - and only while a transfer is actually pending; anything else responds 404, matching the same "don't reveal what you can't see" shape the show endpoint already uses rather than distinguishing "no transfer" from "not your transfer".
PUT /api/schools/:school_id/ownership_transfer/decline marks the school's pending transfer as rejected. Reuses accept's resolve!/ pending_ownership_transfer helpers - the two actions differ only in which status they set - and the same authorization shape (nominee only, pending only, 404 otherwise).
Test coverage93.67% line coverage reported by SimpleCov. |
DNR500
changed the base branch from
main
to
create-and-get-ownership-transfer
September 18, 2026 16:19
DNR500
marked this pull request as ready for review
September 18, 2026 16:50
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Address the concurrency, ownership-transition, and self-nomination authorization issues.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds authenticated endpoints for nominees to accept or decline school ownership transfers.
Changes:
- Adds accept/decline routes and controller actions.
- Restricts responses to nominated users with pending transfers.
- Adds authorization and request coverage.
File summaries
| File | Summary |
|---|---|
spec/features/ownership_transfer/declining_an_ownership_transfer_spec.rb |
Adds decline endpoint coverage. |
spec/features/ownership_transfer/accepting_an_ownership_transfer_spec.rb |
Adds acceptance endpoint coverage. |
config/routes.rb |
Registers accept and decline routes. |
app/models/ability.rb |
Adds transfer-response permissions; self-nominations remain insufficiently restricted. |
app/controllers/api/ownership_transfers_controller.rb |
Resolves transfers, but lacks atomic locking and does not perform the ownership transition. |
Review details
Suppressed comments (1)
app/models/ability.rb:46
- This predicate only checks the nominated ID, so an owner can nominate their own ID (the create path permits any existing owner/teacher) and then pass this authorization as both requester and nominee. That violates the endpoint contract that the requester cannot accept or decline their own request; exclude
requested_by_user_idhere (or reject self-nominations when creating the transfer).
can %i[accept decline], OwnershipTransfer do |transfer|
user.id == transfer.nominated_user_id
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This adds the two endpoints that let the person being asked to take over a school actually respond — accepting it, or turning it down.
What's changed?
PUT /api/schools/:school_id/ownership_transfer/accept— the nominee accepts. The transfer's status becomes "completed".PUT /api/schools/:school_id/ownership_transfer/decline— the nominee turns it down. The transfer's status becomes "rejected".Both endpoints:
Examples
Accepting or declining, as the nominee:
(empty body — the frontend already knows what happened because it made the request; it re-checks the transfer's status separately if it needs to)
If you're not the nominee, or there's nothing waiting to be responded to:
Other responses:
401(no token),403(you're a student — students were never allowed near this feature in the first place).