Skip to content

Handle a violated composite foreign key in db_belongs_to on PostgreSQL - #83

Draft
aitorlb wants to merge 1 commit into
masterfrom
composite-foreign-key-errors
Draft

aitorlb wants to merge 1 commit into
masterfrom
composite-foreign-key-errors

Conversation

@aitorlb

@aitorlb aitorlb commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Description

Draft. 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 the adapter returned that whole list as a single column name, so no validator matched and the ActiveRecord::InvalidForeignKey was raised instead.

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.

Why it matters: in career-services-backend (CASE-4447), 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 became an exception.

  • PostgresqlAdapter.foreign_key_error_column splits the key's columns.
  • Spec: the adapter extracts every column of a composite key; and 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.

How to test

Locally on PostgreSQL 16: spec/adapters/foreign_key_error_column_spec.rb and spec/validations/db_belongs_to_spec.rb pass for PostgreSQL (26 examples in the postgresql group, 0 failures) and for the adapter specs. The SQLite and MySQL groups could not run in this environment (no MySQL server, and the SQLite examples stop at setup with Gem::LoadError: this environment's bundle has no sqlite3); CI covers them. RuboCop is clean on the changed lines (the one offense in db_belongs_to_spec.rb:145 is already on master).

🤖 Generated with Claude Code

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.

1 participant