diff --git a/models/BaseEntity.cfc b/models/BaseEntity.cfc index 043ae6b9..8900ec48 100644 --- a/models/BaseEntity.cfc +++ b/models/BaseEntity.cfc @@ -439,6 +439,15 @@ component accessors="true" { return arrayWrap( variables._key ); } + /** + * Returns the timestamp fields updated by `touch`. + * + * @return [String] + */ + public array function timestampFields() { + return [ "createdDate", "modifiedDate" ]; + } + /** * Returns the column name for the primary key. * @@ -1414,6 +1423,44 @@ component accessors="true" { return save(); } + /** + * Updates the configured timestamp fields using a new query without changing + * the current entity state. + * + * @options Any options to pass to `queryExecute`. Default: {}. + * + * @return quick.models.BaseEntity + */ + public any function touch( struct options = {} ) { + guardAgainstNotLoaded( "This instance is not loaded so it cannot be touched." ); + guardReadOnly(); + var timestamp = now(); + var timestampAttributes = timestampFields().reduce( function( attributes, field ) { + arguments.attributes[ arguments.field ] = timestamp; + return arguments.attributes; + }, {} ); + guardAgainstReadOnlyAttributes( timestampAttributes ); + + var builder = newQuery().where( function( q ) { + keyNames().each( function( keyName ) { + q.where( + arguments.keyName, + variables._originalAttributes[ retrieveColumnForAlias( arguments.keyName ) ] + ); + } ); + } ); + builder + .getQB() + .update( + timestampAttributes.map( function( field, value ) { + return builder.generateQueryParamStruct( field, value ); + } ), + arguments.options + ); + + return this; + } + /** * Creates a new entity with the given attributes and then saves the entity. * diff --git a/tests/resources/app/models/CustomTimestampUser.cfc b/tests/resources/app/models/CustomTimestampUser.cfc new file mode 100644 index 00000000..7c3866d8 --- /dev/null +++ b/tests/resources/app/models/CustomTimestampUser.cfc @@ -0,0 +1,15 @@ +component + extends ="quick.models.BaseEntity" + accessors="true" + table ="users" +{ + + property name="id"; + property name="createdDate" column="created_date"; + property name="modifiedDate" column="modified_date"; + + public array function timestampFields() { + return [ "createdDate" ]; + } + +} diff --git a/tests/specs/integration/BaseEntity/SaveSpec.cfc b/tests/specs/integration/BaseEntity/SaveSpec.cfc index 9ffc5f6d..a3b99ef1 100644 --- a/tests/specs/integration/BaseEntity/SaveSpec.cfc +++ b/tests/specs/integration/BaseEntity/SaveSpec.cfc @@ -76,6 +76,48 @@ component extends="tests.resources.ModuleIntegrationSpec" { expect( userRowsPostSave ).toHaveLength( 5 ); } ); + it( "can touch an entity timestamp", function() { + var user = getInstance( "User" ).findOrFail( 1 ); + var originalCreated = user.getCreatedDate(); + var originalModified = user.getModifiedDate(); + var originalFirstName = user.getFirstName(); + var originalId = user.getId(); + var dirtyFirstName = "This must not be persisted"; + var dirtyId = 9999; + + user.setFirstName( dirtyFirstName ); + user.setId( dirtyId ); + + user.touch(); + + expect( dateCompare( user.getCreatedDate(), originalCreated ) ).toBe( 0 ); + expect( dateCompare( user.getModifiedDate(), originalModified ) ).toBe( 0 ); + expect( user.getFirstName() ).toBe( dirtyFirstName ); + expect( user.getId() ).toBe( dirtyId ); + expect( user.isDirty( "firstName" ) ).toBeTrue(); + expect( user.isDirty( "id" ) ).toBeTrue(); + expect( user.isDirty( "createdDate" ) ).toBeFalse(); + expect( user.isDirty( "modifiedDate" ) ).toBeFalse(); + var freshUser = getInstance( "User" ).findOrFail( originalId ); + expect( dateCompare( freshUser.getCreatedDate(), originalCreated ) ).toBe( 1 ); + expect( dateCompare( freshUser.getModifiedDate(), originalModified ) ).toBe( 1 ); + expect( freshUser.getFirstName() ).toBe( originalFirstName ); + } ); + + it( "can override the timestamp fields used by touch", function() { + var user = getInstance( "CustomTimestampUser" ).findOrFail( 1 ); + var originalCreated = user.getCreatedDate(); + var originalModified = user.getModifiedDate(); + + user.touch(); + + expect( dateCompare( user.getCreatedDate(), originalCreated ) ).toBe( 0 ); + expect( dateCompare( user.getModifiedDate(), originalModified ) ).toBe( 0 ); + var freshUser = user.fresh(); + expect( dateCompare( freshUser.getCreatedDate(), originalCreated ) ).toBe( 1 ); + expect( dateCompare( freshUser.getModifiedDate(), originalModified ) ).toBe( 0 ); + } ); + it( "does not allow updating of column where update=false in property", function() { var existingUser = getInstance( "User" ).find( 1 ); existingUser.setEmail( "test2@test.com" );