diff --git a/models/BaseEntity.cfc b/models/BaseEntity.cfc index c900d867..7c5d1dd0 100644 --- a/models/BaseEntity.cfc +++ b/models/BaseEntity.cfc @@ -3401,6 +3401,9 @@ component accessors="true" { ) { variables._virtualAttributes.append( runtimeAttribute.name ); } + if ( runtimeAttribute.virtual && runtimeAttribute.keyExists( "defaultValue" ) ) { + forceAssignAttribute( runtimeAttribute.name, runtimeAttribute.defaultValue ); + } } if ( isDiscriminatedChild() ) { assignAttribute( @@ -3678,18 +3681,39 @@ component accessors="true" { /** * Creates a virtual attribute for the given name. * - * @name The attribute name to create. + * @name The attribute name to create. + * @defaultValue The default value for the virtual attribute. + * @excludeFromMemento Whether to exclude the virtual attribute from mementos. * * @return quick.models.BaseEntity */ - public any function appendVirtualAttribute( required string name, boolean excludeFromMemento = false ) { + public any function appendVirtualAttribute( + required string name, + any defaultValue, + boolean excludeFromMemento = false + ) { if ( isNull( retrieveAttributeDefinition( arguments.name ) ) ) { - var attr = paramAttribute( { + var attributeDefinition = { "name" : arguments.name, "virtual" : true, "exclude" : arguments.excludeFromMemento - } ); + }; + if ( arguments.keyExists( "defaultValue" ) ) { + attributeDefinition.defaultValue = arguments.defaultValue; + } + var attr = paramAttribute( attributeDefinition ); registerRuntimeAttribute( attr ); + if ( + !arguments.excludeFromMemento && + structKeyExists( this, "memento" ) && + structKeyExists( this.memento, "defaultIncludes" ) && + !arrayContainsNoCase( this.memento.defaultIncludes, arguments.name ) + ) { + this.memento.defaultIncludes.append( arguments.name ); + } + if ( arguments.keyExists( "defaultValue" ) ) { + forceAssignAttribute( arguments.name, arguments.defaultValue ); + } } return this; } diff --git a/models/QuickBuilder.cfc b/models/QuickBuilder.cfc index 16f392d6..730d58b3 100644 --- a/models/QuickBuilder.cfc +++ b/models/QuickBuilder.cfc @@ -1044,11 +1044,17 @@ component accessors="true" transientCache="false" { /** * Creates a virtual attribute for the given name. * - * @name The attribute name to create. + * @name The attribute name to create. + * @defaultValue The default value for the virtual attribute. + * @excludeFromMemento Whether to exclude the virtual attribute from mementos. * * @return quick.models.BaseEntity */ - public any function appendVirtualAttribute( required string name, boolean excludeFromMemento = false ) { + public any function appendVirtualAttribute( + required string name, + any defaultValue, + boolean excludeFromMemento = false + ) { getEntity().appendVirtualAttribute( argumentCollection = arguments ); return this; } diff --git a/tests/specs/integration/BaseEntity/AttributeSpec.cfc b/tests/specs/integration/BaseEntity/AttributeSpec.cfc index 54d73460..7e9e8328 100644 --- a/tests/specs/integration/BaseEntity/AttributeSpec.cfc +++ b/tests/specs/integration/BaseEntity/AttributeSpec.cfc @@ -30,6 +30,23 @@ component extends="tests.resources.ModuleIntegrationSpec" { expect( getInstance( "User" ).hasAttribute( "spawnedVirtual" ) ).toBeFalse(); } ); + it( "can provide a default for a virtual attribute", function() { + var user = getInstance( "User" ).appendVirtualAttribute( "hasPosts", false ); + var newUser = user.newEntity(); + + expect( serializeJSON( user.getHasPosts() ) ).toBe( "false" ); + expect( serializeJSON( user.getMemento().hasPosts ) ).toBe( "false" ); + expect( serializeJSON( newUser.getHasPosts() ) ).toBe( "false" ); + expect( serializeJSON( newUser.getMemento().hasPosts ) ).toBe( "false" ); + } ); + + it( "can exclude a defaulted virtual attribute from mementos", function() { + var user = getInstance( "User" ).appendVirtualAttribute( "internalFlag", false, true ); + + expect( serializeJSON( user.getInternalFlag() ) ).toBe( "false" ); + expect( user.getMemento() ).notToHaveKey( "internalFlag" ); + } ); + it( "can get any attribute using the `getColumnName` magic methods", function() { var user = getInstance( "User" ).find( 1 ); expect( user.getId() ).toBe( 1 );