From 05c0241c6bccb8dfc7d315eb716480ec4e793b5f Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 28 Aug 2026 16:13:13 -0600 Subject: [PATCH] refactor: remove soft delete closures --- models/QuickBuilder.cfc | 53 +++++++++---------- .../BaseEntity/SoftDeletesSpec.cfc | 8 +++ 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/models/QuickBuilder.cfc b/models/QuickBuilder.cfc index 4d6b0308..4060b3e0 100644 --- a/models/QuickBuilder.cfc +++ b/models/QuickBuilder.cfc @@ -674,6 +674,28 @@ component accessors="true" transientCache="false" { variables.qb.setWheres( wheres ); } + /** + * Adds grouped primary-key constraints for bulk delete operations. + */ + private void function addIdConstraints( required array ids ) { + if ( arrayIsEmpty( arguments.ids ) ) { + return; + } + + var idConstraints = variables.qb.forNestedWhere(); + var keyNames = getEntity().keyNames(); + for ( var id in arguments.ids ) { + var values = arrayWrap( id ); + getEntity().guardAgainstKeyLengthMismatch( values ); + var keyConstraints = idConstraints.forNestedWhere(); + for ( var i = 1; i <= keyNames.len(); i++ ) { + keyConstraints.where( keyNames[ i ], values[ i ] ); + } + idConstraints.addNestedWhereQuery( keyConstraints, "or" ); + } + variables.qb.addNestedWhereQuery( idConstraints ); + } + /** * Deletes matching entities according to the configured query. * @@ -687,20 +709,7 @@ component accessors="true" transientCache="false" { */ public struct function deleteAll( array ids = [] ) { getEntity().guardReadOnly(); - if ( !arrayIsEmpty( arguments.ids ) ) { - var idConstraints = variables.qb.forNestedWhere(); - for ( var id in arguments.ids ) { - var values = arrayWrap( id ); - getEntity().guardAgainstKeyLengthMismatch( values ); - var keyConstraints = idConstraints.forNestedWhere(); - var keyNames = getEntity().keyNames(); - for ( var i = 1; i <= keyNames.len(); i++ ) { - keyConstraints.where( keyNames[ i ], values[ i ] ); - } - idConstraints.addNestedWhereQuery( keyConstraints, "or" ); - } - variables.qb.addNestedWhereQuery( idConstraints ); - } + addIdConstraints( arguments.ids ); if ( getEntity().usesSoftDeletes() ) { activateGlobalScopes(); return updateAll( { "#getEntity().retrieveSoftDeleteColumn()#" : now() } ); @@ -727,21 +736,7 @@ component accessors="true" transientCache="false" { */ public struct function forceDeleteAll( array ids = [] ) { getEntity().guardReadOnly(); - if ( !arrayIsEmpty( arguments.ids ) ) { - variables.qb.where( function( q1 ) { - ids.each( function( id ) { - var values = arrayWrap( id ); - getEntity().guardAgainstKeyLengthMismatch( values ); - q1.orWhere( function( q2 ) { - getEntity() - .keyNames() - .each( function( keyName, i ) { - q2.where( keyName, values[ i ] ); - } ); - } ); - } ); - } ); - } + addIdConstraints( arguments.ids ); return variables.qb.delete(); } diff --git a/tests/specs/integration/BaseEntity/SoftDeletesSpec.cfc b/tests/specs/integration/BaseEntity/SoftDeletesSpec.cfc index 2b30768c..9a0fa73d 100644 --- a/tests/specs/integration/BaseEntity/SoftDeletesSpec.cfc +++ b/tests/specs/integration/BaseEntity/SoftDeletesSpec.cfc @@ -34,6 +34,14 @@ component extends="tests.resources.ModuleIntegrationSpec" { trashedUser.forceDelete(); expect( getInstance( "SoftDeleteUser" ).withTrashed().find( 1 ) ).toBeNull(); } ); + + it( "can force delete multiple entities by id", function() { + getInstance( "SoftDeleteUser" ).forceDeleteAll( [ 1, 2 ] ); + + expect( getInstance( "SoftDeleteUser" ).withTrashed().find( 1 ) ).toBeNull(); + expect( getInstance( "SoftDeleteUser" ).withTrashed().find( 2 ) ).toBeNull(); + expect( getInstance( "SoftDeleteUser" ).findOrFail( 3 ).getUsername() ).toBe( "janedoe" ); + } ); } ); }