Skip to content

feat: add on_delete_message and on_update_message options to references - #911

Merged
zachdaniel merged 2 commits into
mainfrom
fk-restrict-message
Oct 27, 2026
Merged

zachdaniel merged 2 commits into
mainfrom
fk-restrict-message

Conversation

@sevenseacat

Copy link
Copy Markdown
Contributor

Deleting a destination record (or updating its referenced attribute) while a foreign key still references it returned a hardcoded "would leave records behind" error. There was no way to customise it: not on the reference, and not with foreign_key_names either.

on_delete_message / on_update_message

This adds two options to reference:

references do
  reference :post,
    on_delete: :restrict,
    on_delete_message: "post still has comments",
    on_update_message: "post is still referenced by comments"
end
  • add_related_foreign_key_constraints now gets the action type (:delete, or :update for updates and upserts) and uses the matching message from the reference. If no message is set it falls back to "would leave records behind", so existing behaviour doesn't change.
  • I also flattened that function. Previously its nested cases repeated the constraint registration four times. Now it works out the constraint name and message once, then has a single default_constraint_match_type/2 case, the same shape as add_my_foreign_key_constraints.
  • The new options don't change migration snapshots, because the migration generator copies reference fields one by one.

foreign_key_names overrides were being shadowed

Ecto uses the most recently added matching constraint. Since 1f6621b8, add_configured_foreign_key_constraints has run before add_my_foreign_key_constraints and add_related_foreign_key_constraints. As a result, a foreign_key_names entry for a foreign key that a relationship already covers has its key and message silently replaced by the derived one, on both create and delete. When the feature was introduced in 830a4ca6, it ran last. This PR moves it back to the end of each branch, with a comment explaining why the order matters.

Tests

  • test/sourced_foreign_key_test.exs: custom on_delete_message and on_update_message. This reuses SourcedFkParent/SourcedFkChild, with the parent's id made writable and an :update action added.
  • test/update_test.exs: the default message when updating the id of a referenced Post. The delete default is still covered by test/destroy_test.exs.
  • test/references_test.exs: foreign_key_names messages take precedence on create (Permalink) and on destroy (Container). Both tests fail without the fix.

…rences

Deleting or updating a destination record that is still referenced
returned a hardcoded "would leave records behind" error. The message
can now be customised per reference with `on_delete_message` and
`on_update_message`, falling back to the existing message when unset.

Also flattens `add_related_foreign_key_constraints` so the constraint
name and message are resolved once, matching
`add_my_foreign_key_constraints`.
…reign keys

Ecto uses the most recently added matching constraint, and since
1f6621b `add_configured_foreign_key_constraints` ran before
`add_my_foreign_key_constraints` and `add_related_foreign_key_constraints`.
Any `foreign_key_names` entry for a foreign key that a relationship
already covered had its key and message shadowed. Configured foreign
keys are now added last again.
@zachdaniel

Copy link
Copy Markdown
Contributor

🚀 Thank you for your contribution! 🚀

@zachdaniel
zachdaniel merged commit 02bf3a7 into main Oct 27, 2026
116 of 126 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