fix(polymorphic): skip pre-delete FK nullify when datasource cascades#291
Merged
bexchauveto merged 2 commits intomainfrom Apr 29, 2026
Merged
fix(polymorphic): skip pre-delete FK nullify when datasource cascades#291bexchauveto merged 2 commits intomainfrom
bexchauveto merged 2 commits intomainfrom
Conversation
…#289) Forest's delete route nullifies polymorphic FKs on the foreign collection before delete. This breaks ActiveRecord parents that declare `dependent: :destroy`: the UPDATE either violates a NOT NULL constraint on the FK or fails Rails' default `belongs_to` presence validation, preventing deletion entirely. Add a `cascade_on_delete` flag to PolymorphicOneToMany/OneToOne schemas (default false). The AR datasource sets it to true when the association declares `dependent: :destroy`, `:destroy_async`, or `:delete_all`, and `delete_records` skips its manual nullify when the flag is set. Other datasources (Mongoid, RPC, Snowflake) keep current behavior.
6 new issues
|
Mirrors the existing exclusion for many_to_many_schema.rb after adding the cascade_on_delete kwarg pushed param count past Max=5.
90902ed to
198e837
Compare
Member
|
🎉 This PR is included in version 1.27.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
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
Fixes #289. The agent's
delete_recordsaction pre-emptivelyUPDATEs polymorphic child rows toNULLboth the FK id and type column before issuing the parent delete. For ActiveRecord parents that declaredependent: :destroy, this breaks deletion entirely:belongs_to :foo, polymorphic: trueadds a presence validator → the UPDATE fails withValidation failed: <relation> must exist.null: falseFK column → the UPDATE violates the NOT NULL constraint.dependent: :destroy: by the time Rails' destroy callback runs, itsWHERE notable_id = ? AND notable_type = ?matches zero rows, silently orphaning the children.The reporter showed the bug works correctly from the Rails console (Rails handles the cascade), but fails through Forest because of this pre-delete UPDATE.
Fix
Plumb a
cascade_on_deleteflag through to the polymorphic schemas; when true, the agent skips its manual nullify and lets the datasource handle the cascade itself.cascade_on_delete(defaultfalse) toPolymorphicOneToManySchemaandPolymorphicOneToOneSchema. The flag rides through the RPC layer for free (**schemareconstruction +.to_jsonserializer dumps all instance vars).cascade_on_delete: truewhen the association declaresdependent: :destroy,:destroy_async, or:delete_all.delete_recordsskips the polymorphic-cleanup loop body whenfield_schema.cascade_on_deleteis truthy. Guarded withrespond_to?for compat with anything the customizer might inject.Mongoid / Snowflake / RPC keep the existing behavior (
cascade_on_deletedefaults to false, so the manual cleanup still runs).Test plan
forest_admin_datasource_toolkitrspec — 463 examples, 0 failures (added defaults + explicit-flag specs on both schemas)forest_admin_datasource_active_recordrspec — 147 examples, 0 failures (added:cascade_on_delete=trueforProject.has_many :members, as: :memberable, dependent: :destroy;=falseforUser.has_one :address, as: :addressable)forest_admin_agentrspec — 683 examples, 0 failures (newwith polymorphic relation that cascades on deletecontext: foreign collection'supdateis NOT called, parentdeletestill is)forest_admin_datasource_rpcrspec — 160 examples, 0 failures (sanity: schema round-trips through RPC unchanged)Customerwithhas_many :notes, as: :notable, dependent: :destroyandnotable_id/notable_typenull: false— delete now performs the same SQL ascustomer.destroy!(children deleted via cascade, then parent), instead of erroring on the nullify.Note
Skip FK nullification before delete for polymorphic relations when datasource cascades
cascade_on_deleteflag (defaultfalse) toPolymorphicOneToManySchemaandPolymorphicOneToOneSchemain the toolkit.dependentoption (:destroy,:destroy_async, or:delete_all) via a newcascade_on_delete?helper inCollection.Delete#delete_recordsnow skips the pre-delete FK nullify step for polymorphic relations whencascade_on_deleteistrue, avoiding redundant or conflicting updates before the DB cascade fires.Macroscope summarized 198e837.