From c63e1d8501ed4f16c9fcac5645a776c6999f65ff Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 22 Aug 2026 06:00:22 -0600 Subject: [PATCH] Refresh database-generated attributes on save Closes #58 --- models/BaseEntity.cfc | 41 ++++++++++++++++++- .../app/models/DatabaseGeneratedUser.cfc | 9 ++++ .../specs/integration/BaseEntity/SaveSpec.cfc | 12 ++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/resources/app/models/DatabaseGeneratedUser.cfc diff --git a/models/BaseEntity.cfc b/models/BaseEntity.cfc index a3f7a943..f86d575f 100644 --- a/models/BaseEntity.cfc +++ b/models/BaseEntity.cfc @@ -1183,6 +1183,7 @@ component accessors="true" { } ), arguments.options ); + refreshAttributesOnSave(); assignOriginalAttributes( retrieveAttributesData() ); markLoaded(); fireEvent( @@ -1217,8 +1218,9 @@ component accessors="true" { result.result[ getParentDefinition().joincolumn ] = variables._data[ getParentDefinition().joinColumn ]; } retrieveKeyType().postInsert( this, result ); - assignOriginalAttributes( retrieveAttributesData() ); markLoaded(); + refreshAttributesOnSave(); + assignOriginalAttributes( retrieveAttributesData() ); fireEvent( "postInsert", { @@ -1250,6 +1252,39 @@ component accessors="true" { return this; } + /** + * Refreshes attributes whose values are generated or changed by the database + * during persistence. + */ + private void function refreshAttributesOnSave() { + var attributesToRefresh = variables._attributes.filter( function( name, attribute ) { + return attribute.refreshOnSave; + } ); + + if ( attributesToRefresh.isEmpty() ) { + return; + } + + var refreshedEntity = newQuery() + .withoutGlobalScope() + .where( function( q ) { + arrayZipEach( [ keyNames(), keyValues() ], function( keyName, keyValue ) { + q.where( keyName, keyValue ); + } ); + } ) + .first(); + if ( isNull( refreshedEntity ) ) { + return; + } + + var refreshedData = refreshedEntity.retrieveAttributesData( withNulls = true ); + attributesToRefresh.each( function( name, attribute ) { + var value = refreshedData[ attribute.column ]; + variables._data[ attribute.column ] = isNull( value ) ? javacast( "null", "" ) : value; + variables[ attribute.name ] = isNull( value ) ? javacast( "null", "" ) : value; + } ); + } + /** * Deletes the entity from the database. * This function can only be called on loaded entities. @@ -2973,12 +3008,16 @@ component accessors="true" { param attr.sqltype = ""; param attr.insert = true; param attr.update = true; + param attr.refreshOnSave = false; param attr.virtual = false; param attr.exclude = false; param attr.isParentColumn = false; if ( !isBoolean( attr.persistent ) ) { attr.persistent = lCase( trim( attr.persistent & "" ) ) == "true"; } + if ( !isBoolean( attr.refreshOnSave ) ) { + attr.refreshOnSave = lCase( trim( attr.refreshOnSave & "" ) ) == "true"; + } variables._nullValues[ attr.name ] = attr.nullValue; return arguments.attr; } diff --git a/tests/resources/app/models/DatabaseGeneratedUser.cfc b/tests/resources/app/models/DatabaseGeneratedUser.cfc new file mode 100644 index 00000000..8dfd2902 --- /dev/null +++ b/tests/resources/app/models/DatabaseGeneratedUser.cfc @@ -0,0 +1,9 @@ +component extends="quick.models.BaseEntity" accessors="true" table="users" { + + property name="id"; + property name="username"; + property name="firstName" column="first_name"; + property name="lastName" column="last_name"; + property name="createdDate" column="created_date" refreshOnSave="true"; + +} diff --git a/tests/specs/integration/BaseEntity/SaveSpec.cfc b/tests/specs/integration/BaseEntity/SaveSpec.cfc index 61a9d2ef..12cc9776 100644 --- a/tests/specs/integration/BaseEntity/SaveSpec.cfc +++ b/tests/specs/integration/BaseEntity/SaveSpec.cfc @@ -56,6 +56,18 @@ component extends="tests.resources.ModuleIntegrationSpec" { expect( newUser.retrieveAttributesData() ).toHaveKey( "id" ); } ); + it( "retrieves database-generated attributes marked to refresh on save", function() { + var newUser = getInstance( "DatabaseGeneratedUser" ) + .setUsername( "database-timestamp-user" ) + .setFirstName( "Database" ) + .setLastName( "Timestamp" ) + .save(); + + expect( newUser.getCreatedDate() ).notToBe( "" ); + expect( newUser.getCreatedDate() ).toBeDate(); + expect( newUser.isDirty( "createdDate" ) ).toBeFalse(); + } ); + it( "a saved entity is not dirty", function() { var newUser = getInstance( "User" ); newUser.setUsername( "new_user" );