Skip to content

[CASE-4447] Handle a violated composite foreign key in db_belongs_to on PostgreSQL - #83

Merged
aitorlb merged 1 commit into
masterfrom
composite-foreign-key-errors
Oct 27, 2026
Merged

aitorlb merged 1 commit into
masterfrom
composite-foreign-key-errors

Conversation

@aitorlb

@aitorlb aitorlb commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Ticket: CASE-4447

Draft. Related: toptal/career-services-backend#2195 adds the composite keys that hit this, and merges only after this fix is released.

Problem

On PostgreSQL, db_belongs_to turns a foreign-key violation into the association's must exist error only when the key has one column. A composite key reports every column, Key (tenant_id, company_id)=(2, 1) is not present, and PostgresqlAdapter.foreign_key_error_column returned that whole list as a single column name, so no validator matched and the ActiveRecord::InvalidForeignKey was raised instead.

In career-services-backend (CASE-4447, toptal/career-services-backend#2195), every foreign key between two brand-owned tables gets a composite partner over (brand_id, <column>) next to the single-column key. Postgres checks a row's keys in the order of their internal trigger names, so for a parent id that does not exist either key can report it; when it is the composite one, a must exist validation failure becomes an exception.

Fix

columns = error.message[/Key \(([^)]+)\)/, 1]
columns ? columns.split(', ') : []

The adapter now returns each column as a candidate, as the SQLite adapter already does for a multi-column insert. The rescuer picks the candidate with a db_belongs_to, and when there are several it already checks which referenced row is missing (foreign_key_invalid?), so nothing else changes.

Tests

  • spec/adapters/foreign_key_error_column_spec.rb: the adapter extracts every column of a composite key.
  • spec/validations/db_belongs_to_spec.rb: on PostgreSQL, a db_belongs_to :company record pointing at a company of another tenant (single-column key satisfied, composite key violated) gets errors[:company] instead of an exception. Without the fix, that example raises ActiveRecord::InvalidForeignKey.
  • CHANGELOG under Unreleased, next to [CASE-4454] Unique violation without key detail is handled on PostgreSQL #82's entry; one release ships both.

Locally on PostgreSQL 16 both specs pass (26 examples in the postgresql group, 0 failures, plus the adapter specs). RuboCop is clean on the changed lines (the one offense in db_belongs_to_spec.rb:145 is already on master).

Not run

MySQL and SQLite: there was no MySQL server, and the SQLite examples stop at setup with Gem::LoadError because this environment's bundle has no sqlite3. The change touches only the PostgreSQL adapter; CI covers the others.

🤖 Generated with Claude Code

@aitorlb aitorlb self-assigned this Oct 27, 2026
@aitorlb aitorlb changed the title Handle a violated composite foreign key in db_belongs_to on PostgreSQL [CASE-4447] Handle a violated composite foreign key in db_belongs_to on PostgreSQL Oct 27, 2026
@aitorlb
aitorlb marked this pull request as ready for review October 27, 2026 09:38
@aitorlb
aitorlb merged commit e637e9a into master Oct 27, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants