From 7d37dc011f82a857a9456958994b2eb400c71f99 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 22 Aug 2026 06:36:12 -0600 Subject: [PATCH 1/3] Add without for configured eager loads Closes #48 --- models/QuickBuilder.cfc | 25 +++++++++++++++++++ .../Relationships/EagerLoadingSpec.cfc | 12 +++++++++ 2 files changed, 37 insertions(+) diff --git a/models/QuickBuilder.cfc b/models/QuickBuilder.cfc index e6addb5..9ba6fd4 100644 --- a/models/QuickBuilder.cfc +++ b/models/QuickBuilder.cfc @@ -591,6 +591,31 @@ component accessors="true" transientCache="false" { return this; } + /** + * Removes one or more relationships from the eager-load list. Omitting the + * argument removes every configured eager load. + * + * @relationName A relationship name or array of relationship names to remove. + * + * @return QuickBuilder + */ + public any function without( any relationName ) { + if ( isNull( arguments.relationName ) ) { + variables._eagerLoad = []; + return this; + } + + var exclusions = arrayWrap( arguments.relationName ); + variables._eagerLoad = variables._eagerLoad.filter( function( eagerLoad ) { + var path = isStruct( arguments.eagerLoad ) ? arguments.eagerLoad.keyArray()[ 1 ] : arguments.eagerLoad; + return !exclusions.some( function( exclusion ) { + return compareNoCase( path, exclusion ) == 0 || + compareNoCase( left( path, len( exclusion ) + 1 ), exclusion & "." ) == 0; + } ); + } ); + return this; + } + /** * Eager loads the configured relations for the retrieved entities. * Returns the retrieved entities eager loaded with the configured diff --git a/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc b/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc index bb4972d..b423f5b 100644 --- a/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc +++ b/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc @@ -685,6 +685,18 @@ component extends="tests.resources.ModuleIntegrationSpec" { ); } } ); + + it( "can disable an automatically eager loaded relationship", () => { + var posts = getInstance( "EagerLoadedPost" ) + .without( "comments" ) + .preventLazyLoading() + .get(); + + expect( posts ).toHaveLength( 4 ); + expect( posts[ 1 ].isRelationshipLoaded( "comments" ) ).toBeFalse(); + expect( () => posts[ 1 ].getComments() ).toThrow( type = "QuickLazyLoadingException" ); + expect( variables.queries ).toHaveLength( 1, "Only the posts query should execute." ); + } ); } ); describe( "multiple nested eager loads", () => { From 655cc4730d188b73953e28aacc740a4024bfdff4 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 24 Aug 2026 15:02:36 -0600 Subject: [PATCH 2/3] test: separate without from clearing eager loads --- .../Relationships/EagerLoadingSpec.cfc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc b/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc index b423f5b..ac51ded 100644 --- a/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc +++ b/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc @@ -697,6 +697,23 @@ component extends="tests.resources.ModuleIntegrationSpec" { expect( () => posts[ 1 ].getComments() ).toThrow( type = "QuickLazyLoadingException" ); expect( variables.queries ).toHaveLength( 1, "Only the posts query should execute." ); } ); + + it( "does not clear eager loads when without is called without arguments", () => { + var posts = getInstance( "EagerLoadedPost" ).without().preventLazyLoading().get(); + + expect( posts ).toHaveLength( 4 ); + expect( posts[ 1 ].isRelationshipLoaded( "comments" ) ).toBeTrue(); + expect( variables.queries ).toHaveLength( 2 ); + } ); + + it( "can explicitly clear all eager loads", () => { + var posts = getInstance( "EagerLoadedPost" ).clearEagerLoads().preventLazyLoading().get(); + + expect( posts ).toHaveLength( 4 ); + expect( posts[ 1 ].isRelationshipLoaded( "comments" ) ).toBeFalse(); + expect( () => posts[ 1 ].getComments() ).toThrow( type = "QuickLazyLoadingException" ); + expect( variables.queries ).toHaveLength( 1, "Only the posts query should execute." ); + } ); } ); describe( "multiple nested eager loads", () => { From 0156962b61b6aca54574e101b59a2422fded0668 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 24 Aug 2026 15:12:42 -0600 Subject: [PATCH 3/3] feat: add explicit eager load clearing --- models/QuickBuilder.cfc | 13 +++++++++++-- .../BaseEntity/Relationships/EagerLoadingSpec.cfc | 10 ++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/models/QuickBuilder.cfc b/models/QuickBuilder.cfc index 9ba6fd4..3ae54ed 100644 --- a/models/QuickBuilder.cfc +++ b/models/QuickBuilder.cfc @@ -593,7 +593,7 @@ component accessors="true" transientCache="false" { /** * Removes one or more relationships from the eager-load list. Omitting the - * argument removes every configured eager load. + * argument leaves the eager-load list unchanged. * * @relationName A relationship name or array of relationship names to remove. * @@ -601,7 +601,6 @@ component accessors="true" transientCache="false" { */ public any function without( any relationName ) { if ( isNull( arguments.relationName ) ) { - variables._eagerLoad = []; return this; } @@ -616,6 +615,16 @@ component accessors="true" transientCache="false" { return this; } + /** + * Removes every configured eager load. + * + * @return QuickBuilder + */ + public any function clearEagerLoads() { + variables._eagerLoad = []; + return this; + } + /** * Eager loads the configured relations for the retrieved entities. * Returns the retrieved entities eager loaded with the configured diff --git a/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc b/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc index ac51ded..4d45318 100644 --- a/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc +++ b/tests/specs/integration/BaseEntity/Relationships/EagerLoadingSpec.cfc @@ -699,7 +699,10 @@ component extends="tests.resources.ModuleIntegrationSpec" { } ); it( "does not clear eager loads when without is called without arguments", () => { - var posts = getInstance( "EagerLoadedPost" ).without().preventLazyLoading().get(); + var posts = getInstance( "EagerLoadedPost" ) + .without() + .preventLazyLoading() + .get(); expect( posts ).toHaveLength( 4 ); expect( posts[ 1 ].isRelationshipLoaded( "comments" ) ).toBeTrue(); @@ -707,7 +710,10 @@ component extends="tests.resources.ModuleIntegrationSpec" { } ); it( "can explicitly clear all eager loads", () => { - var posts = getInstance( "EagerLoadedPost" ).clearEagerLoads().preventLazyLoading().get(); + var posts = getInstance( "EagerLoadedPost" ) + .clearEagerLoads() + .preventLazyLoading() + .get(); expect( posts ).toHaveLength( 4 ); expect( posts[ 1 ].isRelationshipLoaded( "comments" ) ).toBeFalse();