Skip to content

Commit

Permalink
fix(Child Entities): Merge virtual attributes to child entities
Browse files Browse the repository at this point in the history
  • Loading branch information
elpete committed May 31, 2024
2 parents 4dd8496 + 1121485 commit a0ebd1b
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 10 deletions.
27 changes: 24 additions & 3 deletions models/BaseEntity.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ component accessors="true" {
*/
property name="_withoutFiringEvents" persistent="false";


/**
* An array of virtual attribute key names that have been add to this entity
*/
property name="_virtualAttributes" persistent="false";


/**
* A boolean flag indicating that the entity has been loaded from the database.
*/
Expand Down Expand Up @@ -239,6 +246,7 @@ component accessors="true" {
param variables._queryOptions = {};
param variables._attributes = {};
param variables._columns = {};
param variables._virtualAttributes = [];
variables._saving = false;
return this;
}
Expand Down Expand Up @@ -1158,7 +1166,15 @@ component accessors="true" {
fireEvent( "postSave", { entity : this } );

// re-cast
populateAttributes( variables._castCache );
for ( var key in variables._castCache ) {
var castedValue = castValueForGetter(
key,
variables._castCache[ key ],
true
);
variables._data[ retrieveColumnForAlias( key ) ] = castedValue;
variables[ retrieveAliasForColumn( key ) ] = castedValue;
}

return this;
}
Expand Down Expand Up @@ -2751,6 +2767,7 @@ component accessors="true" {
variables._columns[ attr.column ] = attr;
variables._meta.attributes[ arguments.name ] = variables._attributes[ arguments.name ];
variables._meta.originalMetadata.properties.append( variables._attributes[ arguments.name ] );
variables._virtualAttributes.append( arguments.name );
}
return this;
}
Expand Down Expand Up @@ -3249,10 +3266,14 @@ component accessors="true" {
*
* @return any
*/
private any function castValueForGetter( required string key, any value ) {
private any function castValueForGetter(
required string key,
any value,
boolean forceCast = false
) {
arguments.key = retrieveAliasForColumn( arguments.key );

if ( structKeyExists( variables._castCache, arguments.key ) ) {
if ( structKeyExists( variables._castCache, arguments.key ) AND !arguments.forceCast ) {
return variables._castCache[ arguments.key ];
}

Expand Down
6 changes: 3 additions & 3 deletions models/Casts/JsonCast.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ component singleton {
return javacast( "null", "" );
}

if ( arguments.value == "" ) {
return "";
if ( isJSON( arguments.value ) ) {
return deserializeJSON( arguments.value );
}

return deserializeJSON( arguments.value );
return arguments.value;
}

/**
Expand Down
12 changes: 8 additions & 4 deletions models/QuickBuilder.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -1311,13 +1311,17 @@ component accessors="true" transientCache="false" {
].mapping
);

// add any virtual attributes present in the parent entity to child entity
getEntity()
.keyNames()
.each( function( key, i ) {
data[ childClass.keyNames()[ i ] ] = data[ key ];
.get_virtualAttributes()
.each( function( item ) {
childClass.appendVirtualAttribute( item );
} );

return childClass.hydrate( arguments.data );
return childClass
.assignAttributesData( arguments.data )
.assignOriginalAttributes( arguments.data )
.markLoaded();
} else {
return getEntity()
.newEntity()
Expand Down
8 changes: 8 additions & 0 deletions tests/resources/app/models/Comment.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ component
property name="userId" column="user_id";
property name="createdDate" column="created_date";
property name="modifiedDate" column="modified_date";
property name="sentimentAnalysis" casts="JsonCast@quick";

variables._discriminators = [
"InternalComment"
];


function commentable() {
return polymorphicBelongsTo( "commentable" );
}
Expand All @@ -28,4 +30,10 @@ component
return hasManyThrough( [ "commentable", "tags" ] );
}

// chose this sql function as it present in both mysql and sql server
function scopeAddUpperBody(qb){
qb.selectRaw( "UPPER(body) as upperBody" );
appendVirtualAttribute( "upperBody" );
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
component {

function up( schema, query ) {

var defaultJson = '{ "analyzed": true, "magnitude": 0.8, "score": 0.6 }';

schema.alter( "comments", function( table ) {
table.addColumn( table.string( "sentimentAnalysis" ).nullable() );
} );


query.from( "comments" )
.update( { "sentimentAnalysis" = defaultJson } );

}

function down( schema, query ) {
schema.alter( "comments", function( table ) {
table.dropColumn( "sentimentAnalysis" );
} );
}

}
24 changes: 24 additions & 0 deletions tests/specs/integration/BaseEntity/AttributeCastsSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,30 @@ component extends="tests.resources.ModuleIntegrationSpec" {
var product = getInstance( "BaseProduct" ).firstOrFail();
expect( product.getMetadata() ).toBeStruct();
} );

it( "can maintain casts when loading a discriminated child through the parent", () => {
var comment = getInstance( "Comment" ).where( "designation", "internal" ).first();

expect( comment.getSentimentAnalysis() ).notToBeNull();
expect( comment.getSentimentAnalysis() ).toBeStruct();
expect( comment.getSentimentAnalysis() ).toHaveKey( "analyzed" );
expect( comment.getSentimentAnalysis() ).toHaveKey( "magnitude" );
expect( comment.getSentimentAnalysis() ).toHaveKey( "score" );
} );

it( "will recast after saving entity", function() {
var pn = getInstance( "PhoneNumber" ).find( 1 );
pn.setNumber( "111-111-1111" );
pn.setActive( "0" );

expect( pn.getActive() ).toBe( "0" );
expect( pn.getActive() ).toBeNumeric();

pn.save();

expect( pn.getActive() ).toBe( false );
expect( pn.getActive() ).toBeBoolean();
} );
} );
}

Expand Down
11 changes: 11 additions & 0 deletions tests/specs/integration/BaseEntity/ChildClassSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,17 @@ component extends="tests.resources.ModuleIntegrationSpec" {
expect( product.skus().toSQL() ).toBe( "SELECT `product_skus`.`createdDate`, `product_skus`.`deletedDate`, `product_skus`.`designation`, `product_skus`.`id`, `product_skus`.`modifiedDate`, `product_skus`.`productId`, `apparel_skus`.`color`, `apparel_skus`.`cost`, `apparel_skus`.`size1`, `apparel_skus`.`size1Description`, `apparel_skus`.`size1Index` FROM `product_skus` LEFT OUTER JOIN `apparel_skus` ON `product_skus`.`id` = `apparel_skus`.`id` WHERE (`product_skus`.`productId` = ? AND `product_skus`.`productId` IS NOT NULL)" );
} );
} );

it( "Will maintain virtual attributes in the child class when fetching results from the parent class", function() {
var comment = getInstance( "Comment" )
.addUpperBody()
.where( "designation", "internal" )
.get()[ 1 ]

expect( comment.hasAttribute( "upperBody" ) ).toBeTrue(
"Child class should have a virtual attribute 'upperBody'."
);
} );
} );

describe( "Single Table Inheritence Class Spec", function() {
Expand Down

0 comments on commit a0ebd1b

Please sign in to comment.