diff --git a/README.md b/README.md index c6e6edf4..0acce26d 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,22 @@ component extends="quick.models.BaseEntity" { Query caching stores database results, not live Quick entities or loaded relationships. Cache lifetime and invalidation are managed by the CFML engine, so use short lifetimes for data that Quick or another process may update. For application-specific invalidation or distributed caching, cache entity mementos in CacheBox at the service layer and rehydrate them through Quick's public APIs. +### Seeding a Loaded Relationship + +Use `assignRelationship` when you already have the related value and want Quick to return it without running the relationship query. This is especially useful after creating related records: + +```javascript +var user = getInstance( "User" ).findOrFail( 1 ); +var post = user.posts().create( { "body" : "A new post" } ); + +// `getPosts()` now returns this array without querying the database. +user.assignRelationship( "posts", [ post ] ); +``` + +Pass a Quick entity for a singular relationship and an array for a collection relationship. Assigning a value replaces any previously loaded value and marks the relationship as loaded. `assignRelationship` only changes the in-memory entity; it does not save either entity, update foreign keys, attach pivot records, or validate that the value matches the relationship type. + +Call `clearRelationship( "posts" )` to discard the assigned value and loaded marker. The next relationship accessor call can then lazy load the relationship normally, when lazy loading is enabled. + ### Tests and Contributing To run the tests, first clone this repo and run a `box install`. diff --git a/models/BaseEntity.cfc b/models/BaseEntity.cfc index 4c2af054..970387e7 100644 --- a/models/BaseEntity.cfc +++ b/models/BaseEntity.cfc @@ -1559,10 +1559,15 @@ component accessors="true" { } /** - * Assigns a result to a relationship. - * - * @name The name of the relationship to assign. - * @value The result for the relationship. + * Assigns a value to an entity relationship and marks that relationship as loaded. + * Accessing the relationship after assignment returns the supplied value without + * executing its relationship query. This only changes the in-memory entity: it + * does not persist the relationship, synchronize keys, or validate the value. + * Use `clearRelationship( name )` to remove the assigned value and loaded marker. + * + * @name The relationship method name to seed. + * @value The value to return for the relationship, usually an entity for a + * singular relationship or an array for a collection relationship. * * @return quick.models.BaseEntity */ diff --git a/tests/specs/integration/BaseEntity/Relationships/RelationshipLoadingSpec.cfc b/tests/specs/integration/BaseEntity/Relationships/RelationshipLoadingSpec.cfc index 91dcb06e..2bcff6c6 100644 --- a/tests/specs/integration/BaseEntity/Relationships/RelationshipLoadingSpec.cfc +++ b/tests/specs/integration/BaseEntity/Relationships/RelationshipLoadingSpec.cfc @@ -144,6 +144,24 @@ component extends="tests.resources.ModuleIntegrationSpec" { expect( elpete2FavoritePost.getBody() ).toBe( "test body" ); } ); + it( "can seed and clear a loaded relationship", function() { + var user = getInstance( "User" ).findOrFail( 1 ); + var seededPost = getInstance( "Post" ).findOrFail( 1245 ); + variables.queries = []; + + user.assignRelationship( "posts", [ seededPost ] ); + + expect( user.isRelationshipLoaded( "posts" ) ).toBeTrue(); + expect( user.getPosts() ).toHaveLength( 1 ); + expect( user.getPosts()[ 1 ].keyValues() ).toBe( seededPost.keyValues() ); + expect( variables.queries ).toBeEmpty(); + + user.clearRelationship( "posts" ); + expect( user.isRelationshipLoaded( "posts" ) ).toBeFalse(); + expect( user.getPosts() ).toHaveLength( 2 ); + expect( variables.queries ).toHaveLength( 1 ); + } ); + it( "can call exists methods on a relationship class", () => { var elpete = getInstance( "User" ).findOrFail( 1 ); expect( elpete.favoritePost().exists() ).toBeTrue();