Repository navigation
[CASE-4447, CASE-4454] Fix db_belongs_to on foreign key violations - #87
Merged
Merged
Conversation
…it is the record's own table's
…nt's, when every row exists
When the table a foreign key is on has row-level security for the current role, PostgreSQL leaves the key's values out of the violation: DETAIL: Key is not present in table "companies". The rescuer found no column, so db_belongs_to re-raised ActiveRecord::InvalidForeignKey instead of adding the association's error. The message still names the constraint, so its columns are read from the record's table.
AlfonsoUceda
approved these changes
Nov 9, 2026
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.
CASE-4447, CASE-4454 · Draft · Needed by toptal/career-services-backend#2339 and the row-level security enforcement stacked on toptal/career-services-backend#2407
What
db_belongs_torescues anActiveRecord::InvalidForeignKeyraised while saving a record and, if a column of the violated key matches one of the record'sdb_belongs_toassociations, adds that association'smust existerror instead of raising. It matched by column only, so a foreign key of another table could be reported as the record's own:savereturnedfalse,save!raisedRecordInvalidwith a misleading message, and the child's integrity error was gone.Since 2.1.2 every column of a composite key is a candidate, which made the shared tenant column match far more often. In career-services-backend every brand-owned model is about to declare
db_belongs_to :brand, and every composite key there starts withbrand_id, so a child's failure would be read as the parent's brand everywhere.The fix: each adapter now reads the table the violated key is on,
foreign_key_error_table:insert or update on table "tenant_users" violates foreign key constraint …a foreign key constraint fails (db.tenant_users, CONSTRAINT …INSERT INTOorUPDATEstatement.The rescuer handles the violation only when that table is the record's (schema prefix ignored). When the table can't be read, behaviour is unchanged.
Second fix, the fallback's choice. When every candidate column's row exists, the key refuses the combination, as a key that leads with a tenant column does for a parent of another tenant. The rescuer used to give the error to the first matching association, which in such a key is the tenant's, so a record declaring both
db_belongs_to :tenantanddb_belongs_to :companyreported "Tenant must exist" for a company of another tenant. It now gives it to the last, the parent's. Single-column keys are unaffected.Third fix, a violation without key detail. When the table a key is on has row-level security for the current role, PostgreSQL leaves the key's values out of the message,
DETAIL: Key is not present in table "companies"., because they could belong to a row the role cannot see. The rescuer found no column and re-raisedInvalidForeignKey. The message still names the constraint, so the rescuer now reads that constraint's columns from the record's table,foreign_key_error_constrainton the PostgreSQL adapter. It is the foreign-key counterpart of 2.1.2's unique-violation fix, and career-services-backend enforces row-level security on every brand-owned table.Tested
db_belongs_to :companycreates, inafter_create, a child whose composite key(tenant_id, company_id)fails. It now raisesInvalidForeignKey. Without the fix the same example fails: the error was swallowed intoerrors[:company].:tenantand:company, saved with a company of another tenant, getserrors[:company], noterrors[:tenant]. It fails with the first-match fallback.db_belongs_to_row_level_security_spec.rb: under a role held to row-level security, a record whose parent does not exist getserrors[:rls_parent], andcreate!raisesRecordInvalid. A first example checks the premise, that the message has noKey (. Both fail without the fix, withInvalidForeignKey.nilwithout a statement.Changelog entry under Unreleased; releasing 2.1.3 can follow once this merges.
🤖 Generated with Claude Code