diff --git a/README.md b/README.md index 3f5457d0..c6e6edf4 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,35 @@ component { Now that you've seen an example, [dig in to what you can do](https://quick.ortusbooks.com/) with Quick! +### Caching queries + +Quick passes query options through to `queryExecute`, so applications can use the query cache provided by their CFML engine. This works with collection queries and primary-key lookups: + +```javascript +var users = getInstance( "User" ).get( + options = { cachedWithin : createTimeSpan( 0, 0, 5, 0 ) } +); + +var user = getInstance( "User" ).find( + rc.id, + { cachedWithin : createTimeSpan( 0, 0, 5, 0 ) } +); +``` + +An entity can also configure defaults for every query by assigning `_queryOptions` in its pseudo-constructor: + +```javascript +component extends="quick.models.BaseEntity" { + + variables._queryOptions = { + cachedWithin : createTimeSpan( 0, 0, 5, 0 ) + }; + +} +``` + +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. + ### Tests and Contributing To run the tests, first clone this repo and run a `box install`. diff --git a/models/QuickBuilder.cfc b/models/QuickBuilder.cfc index 3e0d798e..b78fb58b 100644 --- a/models/QuickBuilder.cfc +++ b/models/QuickBuilder.cfc @@ -1020,7 +1020,7 @@ component accessors="true" transientCache="false" { idQuery.where( allKeyNames[ i ], arguments.id[ i ] ); } variables.qb.addNestedWhereQuery( idQuery, "and" ); - return this.first(); + return this.first( arguments.options ); } /** diff --git a/tests/specs/integration/BaseEntity/GetSpec.cfc b/tests/specs/integration/BaseEntity/GetSpec.cfc index 6d44af93..ce826a85 100644 --- a/tests/specs/integration/BaseEntity/GetSpec.cfc +++ b/tests/specs/integration/BaseEntity/GetSpec.cfc @@ -1,5 +1,17 @@ component extends="tests.resources.ModuleIntegrationSpec" { + function beforeAll() { + super.beforeAll(); + controller + .getInterceptorService() + .registerInterceptor( interceptorObject = this, interceptorName = "BaseEntityGetSpec" ); + } + + function afterAll() { + controller.getInterceptorService().unregister( "BaseEntityGetSpec" ); + super.afterAll(); + } + function run() { describe( "Get Spec", function() { it( "finds an entity by the primary key", function() { @@ -7,6 +19,19 @@ component extends="tests.resources.ModuleIntegrationSpec" { expect( user.isLoaded() ).toBeTrue( "The user instance should be found and loaded, but was not." ); } ); + it( "passes query options when finding an entity by primary key", function() { + structDelete( request, "baseEntityGetSpecPreQBExecute" ); + + var user = getInstance( "User" ).find( 1, { datasource : "quick" } ); + var executionsWithDatasource = request.baseEntityGetSpecPreQBExecute.filter( function( execution ) { + return execution.options.keyExists( "datasource" ); + } ); + + expect( user ).notToBeNull(); + expect( executionsWithDatasource.len() ).toBeGT( 0 ); + expect( executionsWithDatasource[ 1 ].options.datasource ).toBe( "quick" ); + } ); + it( "returns null if the record cannot be found", function() { expect( getInstance( "User" ).find( 999 ) ).toBeNull( "The user instance should be null because it could not be found, but was not." @@ -438,4 +463,15 @@ component extends="tests.resources.ModuleIntegrationSpec" { } ); } + function preQBExecute( + event, + interceptData, + buffer, + rc, + prc + ) { + param request.baseEntityGetSpecPreQBExecute = []; + request.baseEntityGetSpecPreQBExecute.append( duplicate( arguments.interceptData ) ); + } + }