From 9822182802788ce622513fdb451f3e857c4590a7 Mon Sep 17 00:00:00 2001 From: ryanalbrecht Date: Tue, 11 Jun 2024 15:48:08 -0500 Subject: [PATCH 1/4] Fixed issue with NULL values being inserted into identity columns for MSSQL --- models/BaseEntity.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/BaseEntity.cfc b/models/BaseEntity.cfc index f19a5cc..063e43a 100644 --- a/models/BaseEntity.cfc +++ b/models/BaseEntity.cfc @@ -1094,7 +1094,7 @@ component accessors="true" { var parent = variables._wirebox.getInstance( parentDefinition.meta.fullName ); } - parent.fill( getMemento(), true ).save(); + parent.fill( retrieveAttributesData(), true ).save(); assignAttributesData( { "#parentDefinition.key#" : parent.keyValues()[ 1 ], From a3b034ae5c3c2d1a12122839eded394f5a9921b8 Mon Sep 17 00:00:00 2001 From: ryanalbrecht Date: Wed, 12 Jun 2024 13:52:15 -0500 Subject: [PATCH 2/4] failing tests -fix attribute hash test by precomputing hash -fix issue with comparison test by specifying timestamp precision on table song migration --- .../migrations/2020_08_11_102649_create_songs_table.cfc | 9 +++++++-- tests/specs/integration/BaseEntity/AttributeHashSpec.cfc | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/resources/database/migrations/2020_08_11_102649_create_songs_table.cfc b/tests/resources/database/migrations/2020_08_11_102649_create_songs_table.cfc index b4cec63..6a0b766 100755 --- a/tests/resources/database/migrations/2020_08_11_102649_create_songs_table.cfc +++ b/tests/resources/database/migrations/2020_08_11_102649_create_songs_table.cfc @@ -5,8 +5,13 @@ component { t.increments( "id" ); t.string( "title" ).nullable(); t.string( "download_url" ); - t.timestamp( "created_date" ).withCurrent(); - t.timestamp( "modified_date" ).withCurrent(); + + t.raw( "`created_date` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)" ); + t.raw( "`modified_date` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)" ); + + //proposed qb pull request https://github.com/coldbox-modules/qb/pull/282: + //t.timestamp( "created_date", 6 ).withCurrent( 6 ); + //t.timestamp( "modified_date", 6 ).withCurrent( 6 ); } ); qb.table( "songs" ).insert( [ diff --git a/tests/specs/integration/BaseEntity/AttributeHashSpec.cfc b/tests/specs/integration/BaseEntity/AttributeHashSpec.cfc index fc52a94..46980bd 100644 --- a/tests/specs/integration/BaseEntity/AttributeHashSpec.cfc +++ b/tests/specs/integration/BaseEntity/AttributeHashSpec.cfc @@ -9,18 +9,19 @@ component extends="tests.resources.ModuleIntegrationSpec" { } ); it( "can compute attributes hash correctly", function() { + var passwordHash = hash( "password" ) var user = getInstance( "User" ).populate( { "username" : "JaneDoe", "first_name" : "Jane", "last_name" : "Doe", - "password" : hash( "password" ), + "password" : passwordHash, "non-existant-property" : "any-value" }, true ); - var expectedHash = hash( "first_name=Jane&last_name=Doe&password=5F4DCC3B5AA765D61D8327DEB882CF99&username=JaneDoe" ); + var expectedHash = hash( "first_name=Jane&last_name=Doe&password=#passwordHash#&username=JaneDoe" ); var hash = user.computeAttributesHash( user.retrieveAttributesData() ); expect( expectedHash ).toBe( hash, "The computeAttributesHash method does not return the correct hash" ); } ); From 77f77a3bfc5d7480f29ae47f2964210fa1fa0174 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 12 Jun 2024 15:17:27 -0600 Subject: [PATCH 3/4] formatting --- models/BaseEntity.cfc | 20 +++++++++++++------ models/Casts/JsonCast.cfc | 4 +--- models/QuickBuilder.cfc | 10 ++++++---- .../BaseEntity/AttributeCastsSpec.cfc | 13 ++++-------- .../BaseEntity/AttributeHashSpec.cfc | 2 +- .../integration/BaseEntity/ChildClassSpec.cfc | 3 +-- 6 files changed, 27 insertions(+), 25 deletions(-) diff --git a/models/BaseEntity.cfc b/models/BaseEntity.cfc index 063e43a..8c5c649 100644 --- a/models/BaseEntity.cfc +++ b/models/BaseEntity.cfc @@ -174,7 +174,7 @@ component accessors="true" { /** * An array of virtual attribute key names that have been add to this entity */ - property name="_virtualAttributes" persistent="false"; + property name="_virtualAttributes" persistent="false"; /** @@ -1166,10 +1166,14 @@ component accessors="true" { fireEvent( "postSave", { entity : this } ); // re-cast - for ( var key in variables._castCache) { - var castedValue = castValueForGetter( key, variables._castCache[ key ], true ); + for ( var key in variables._castCache ) { + var castedValue = castValueForGetter( + key, + variables._castCache[ key ], + true + ); variables._data[ retrieveColumnForAlias( key ) ] = castedValue; - variables[ retrieveAliasForColumn( key ) ] = castedValue; + variables[ retrieveAliasForColumn( key ) ] = castedValue; } return this; @@ -3264,10 +3268,14 @@ component accessors="true" { * * @return any */ - private any function castValueForGetter( required string key, any value, boolean forceCast = false ) { + private any function castValueForGetter( + required string key, + any value, + boolean forceCast = false + ) { arguments.key = retrieveAliasForColumn( arguments.key ); - if ( structKeyExists( variables._castCache, arguments.key ) AND !arguments.forceCast ) { + if ( structKeyExists( variables._castCache, arguments.key ) AND !arguments.forceCast ) { return variables._castCache[ arguments.key ]; } diff --git a/models/Casts/JsonCast.cfc b/models/Casts/JsonCast.cfc index d006edc..479b2c9 100644 --- a/models/Casts/JsonCast.cfc +++ b/models/Casts/JsonCast.cfc @@ -14,17 +14,15 @@ component singleton { required string key, any value ) { - if ( isNull( arguments.value ) ) { return javacast( "null", "" ); } - if( isJSON( arguments.value ) ){ + if ( isJSON( arguments.value ) ) { return deserializeJSON( arguments.value ); } return arguments.value; - } /** diff --git a/models/QuickBuilder.cfc b/models/QuickBuilder.cfc index 859b141..2d5cd23 100644 --- a/models/QuickBuilder.cfc +++ b/models/QuickBuilder.cfc @@ -1311,10 +1311,12 @@ component accessors="true" transientCache="false" { ].mapping ); - //add any virtual attributes present in the parent entity to child entity - getEntity().get_virtualAttributes().each( function( item ) { - childClass.appendVirtualAttribute( item ); - } ); + // add any virtual attributes present in the parent entity to child entity + getEntity() + .get_virtualAttributes() + .each( function( item ) { + childClass.appendVirtualAttribute( item ); + } ); return childClass .assignAttributesData( arguments.data ) diff --git a/tests/specs/integration/BaseEntity/AttributeCastsSpec.cfc b/tests/specs/integration/BaseEntity/AttributeCastsSpec.cfc index 2e74d14..818abdf 100644 --- a/tests/specs/integration/BaseEntity/AttributeCastsSpec.cfc +++ b/tests/specs/integration/BaseEntity/AttributeCastsSpec.cfc @@ -112,33 +112,28 @@ component extends="tests.resources.ModuleIntegrationSpec" { } ); it( "can maintain casts when loading a discriminated child through the parent", () => { - var comment = getInstance( "Comment" ) - .where('designation', 'internal') - .first(); + 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); + 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() ).toBe( false ); expect( pn.getActive() ).toBeBoolean(); - } ); - } ); } diff --git a/tests/specs/integration/BaseEntity/AttributeHashSpec.cfc b/tests/specs/integration/BaseEntity/AttributeHashSpec.cfc index 46980bd..42ede58 100644 --- a/tests/specs/integration/BaseEntity/AttributeHashSpec.cfc +++ b/tests/specs/integration/BaseEntity/AttributeHashSpec.cfc @@ -10,7 +10,7 @@ component extends="tests.resources.ModuleIntegrationSpec" { it( "can compute attributes hash correctly", function() { var passwordHash = hash( "password" ) - var user = getInstance( "User" ).populate( + var user = getInstance( "User" ).populate( { "username" : "JaneDoe", "first_name" : "Jane", diff --git a/tests/specs/integration/BaseEntity/ChildClassSpec.cfc b/tests/specs/integration/BaseEntity/ChildClassSpec.cfc index 5f0e1f4..9edd3fc 100644 --- a/tests/specs/integration/BaseEntity/ChildClassSpec.cfc +++ b/tests/specs/integration/BaseEntity/ChildClassSpec.cfc @@ -352,13 +352,12 @@ component extends="tests.resources.ModuleIntegrationSpec" { var comment = getInstance( "Comment" ) .addUpperBody() .where( "designation", "internal" ) - .get()[1] + .get()[ 1 ] expect( comment.hasAttribute( "upperBody" ) ).toBeTrue( "Child class should have a virtual attribute 'upperBody'." ); } ); - } ); describe( "Single Table Inheritence Class Spec", function() { From c27954cc419cabc3d1c544b8c5e96583bb284284 Mon Sep 17 00:00:00 2001 From: ryanalbrecht Date: Thu, 13 Jun 2024 14:23:49 -0500 Subject: [PATCH 4/4] -added test case for regression --- tests/resources/app/models/Comment.cfc | 3 +- tests/resources/app/models/PictureComment.cfc | 9 +++ ...3_134500_create_picture_comments_table.cfc | 41 +++++++++++++ .../integration/BaseEntity/ChildClassSpec.cfc | 57 +++++++++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 tests/resources/app/models/PictureComment.cfc create mode 100644 tests/resources/database/migrations/2024_06_13_134500_create_picture_comments_table.cfc diff --git a/tests/resources/app/models/Comment.cfc b/tests/resources/app/models/Comment.cfc index 69c75ed..5150e64 100644 --- a/tests/resources/app/models/Comment.cfc +++ b/tests/resources/app/models/Comment.cfc @@ -14,7 +14,8 @@ component property name="sentimentAnalysis" casts="JsonCast@quick"; variables._discriminators = [ - "InternalComment" + "InternalComment", + "PictureComment" ]; diff --git a/tests/resources/app/models/PictureComment.cfc b/tests/resources/app/models/PictureComment.cfc new file mode 100644 index 0000000..1f8c338 --- /dev/null +++ b/tests/resources/app/models/PictureComment.cfc @@ -0,0 +1,9 @@ +component + accessors="true" + extends="Comment" + table="pictureComments" + joincolumn="FK_comment" + discriminatorValue="picture" +{ + property name="filename"; +} \ No newline at end of file diff --git a/tests/resources/database/migrations/2024_06_13_134500_create_picture_comments_table.cfc b/tests/resources/database/migrations/2024_06_13_134500_create_picture_comments_table.cfc new file mode 100644 index 0000000..6220a8c --- /dev/null +++ b/tests/resources/database/migrations/2024_06_13_134500_create_picture_comments_table.cfc @@ -0,0 +1,41 @@ +component { + + function up( schema, qb ) { + schema.create( "pictureComments", function( t ) { + t.unsignedInteger( "FK_comment" ) + .references( "id" ) + .onTable( "comments" ) + .onUpdate( "CASCADE" ) + .onDelete( "CASCADE" ); + t.text( "filename" ); + t.primaryKey( "FK_comment" ); + } ); + + qb.newQuery().table( "comments" ).insert( [ + { + "id": 5, + "body": "This is an picture comment. It is very, very picturesque.", + "commentable_id": 1246, + "commentable_type": "Post", + "designation": "picture", + "user_id": 1, + "created_date": "2024-06-13 13:14:22", + "modified_date": "2024-06-13 13:14:22", + "sentimentAnalysis" : '{ "analyzed": true, "magnitude": 0.8, "score": 0.6 }' + } + ] ); + + qb.newQuery().table( "pictureComments" ).insert( [ + { + "FK_comment": 5, + "filename": "bliss.jpg" + } + ] ); + } + + function down( schema, qb ) { + schema.drop( "pictureComments" ); + qb.table( "comments" ).where( "id", 5 ).delete(); + } + +} diff --git a/tests/specs/integration/BaseEntity/ChildClassSpec.cfc b/tests/specs/integration/BaseEntity/ChildClassSpec.cfc index 9edd3fc..d132f75 100644 --- a/tests/specs/integration/BaseEntity/ChildClassSpec.cfc +++ b/tests/specs/integration/BaseEntity/ChildClassSpec.cfc @@ -331,6 +331,63 @@ component extends="tests.resources.ModuleIntegrationSpec" { } ); } ); + + it( "Will return an mix of child classes when retrieving all discriminated comments", function() { + var comments = getInstance( "Comment" ) + .where('designation', '!=', 'public') + .orderBy('id') + .get(); + + expect( comments[1] ).toBeInstanceOf( "InternalComment" ); + expect( comments[2] ).toBeInstanceOf( "PictureComment" ); + } ); + + it( "Will load foreign key when retrieving different child classes through parent", function() { + // comment id 4 = internal comment + var internalMemento = getInstance( "Comment" ) + .findOrFail(4) + .getMemento(); + + expect( internalMemento ).toHaveKey( "FK_comment" ); + expect( internalMemento['FK_comment'] ).toBe( 4 ) + + // comment id 5 = picture comment + var pictureMemento = getInstance( "Comment" ) + .findOrFail(5) + .getMemento(); + + expect( pictureMemento ).toHaveKey( "FK_comment" ); + expect( pictureMemento['FK_comment'] ).toBe( 5 ) + } ); + + it( "Can update each child class when fetching from parent class", function() { + // comment id 4 = internal comment + var internalComment = getInstance( "Comment" ) + .findOrFail(4) + .update({ + reason: 'Super private, ya know?' + }); + + var uInternalComment = getInstance( "Comment" ) + .findOrFail(4); + + expect( uInternalComment).toBeInstanceOf( "internalComment" ); + expect( uInternalComment.getReason() ).toBe('Super private, ya know?'); + + // comment id 5 = picture comment + var pictureComment = getInstance( "Comment" ) + .findOrFail(5) + .update({ + filename: 'Lenna.jpeg' + }); + + var uPictureComment = getInstance( "Comment" ) + .findOrFail(5); + + expect( uPictureComment).toBeInstanceOf( "pictureComment" ); + expect( uPictureComment.getFileName() ).toBe('Lenna.jpeg'); + } ); + it( "returns an array of discriminated entities when loading through a relationship", () => { var post = getInstance( "Post" ).findOrFail( 1245 ); var comments = post.getComments();