diff --git a/ModuleConfig.cfc b/ModuleConfig.cfc index bcb54e07..62a9f12a 100644 --- a/ModuleConfig.cfc +++ b/ModuleConfig.cfc @@ -35,6 +35,7 @@ component { "quickInstanceReady", "quickPreLoad", "quickPostLoad", + "quickPostReplicate", "quickPreSave", "quickPostSave", "quickPreInsert", diff --git a/models/BaseEntity.cfc b/models/BaseEntity.cfc index a3f7a943..afd18419 100644 --- a/models/BaseEntity.cfc +++ b/models/BaseEntity.cfc @@ -1110,6 +1110,31 @@ component accessors="true" { return entityClone; } + /** + * Creates a new, unloaded entity with this entity's attributes except for + * its primary key and any additional excluded attributes. + * + * @except Additional attribute aliases or column names to exclude. + * + * @return quick.models.BaseEntity + */ + public any function replicate( array except = [] ) { + var attributes = retrieveAttributesData( withoutKey = true, withNulls = true ); + for ( var attribute in arguments.except ) { + attributes.delete( retrieveColumnForAlias( attribute ) ); + } + + var replica = newEntity().fill( attributes ); + replica.fireEvent( + "postReplicate", + { + "entity" : replica, + "original" : this + } + ); + return replica; + } + /*=========================================== diff --git a/tests/resources/app/models/Song.cfc b/tests/resources/app/models/Song.cfc index cdab6483..0cc01bca 100644 --- a/tests/resources/app/models/Song.cfc +++ b/tests/resources/app/models/Song.cfc @@ -14,9 +14,13 @@ component extends="quick.models.BaseEntity" accessors="true" { request.preLoadCalled = eventData; } - function postLoad( eventData ) { - request.postLoadCalled = eventData; - } + function postLoad( eventData ) { + request.postLoadCalled = eventData; + } + + function postReplicate( eventData ) { + request.postReplicateCalled = eventData; + } function preInsert( eventData ) { request.preInsertCalled = { diff --git a/tests/specs/integration/BaseEntity/CloneSpec.cfc b/tests/specs/integration/BaseEntity/CloneSpec.cfc index a671cc17..ad4cdaa8 100644 --- a/tests/specs/integration/BaseEntity/CloneSpec.cfc +++ b/tests/specs/integration/BaseEntity/CloneSpec.cfc @@ -26,6 +26,29 @@ component extends="tests.resources.ModuleIntegrationSpec" { expect( clonedUser.isLoaded() ).toBeTrue( "The cloned user instance should be marked as loaded, but was not." ); } ); + it( "can replicate a loaded entity as a new entity without its key", function() { + var user = getInstance( "User" ).findOrFail( 1 ); + var replica = user.replicate(); + + expect( replica.isLoaded() ).toBeFalse(); + expect( replica.retrieveAttributesData() ).notToHaveKey( "id" ); + expect( replica.getUsername() ).toBe( user.getUsername() ); + + replica + .setUsername( "replicated-user" ) + .setEmail( "replicated@example.com" ) + .save(); + expect( replica.isLoaded() ).toBeTrue(); + expect( replica.getId() ).notToBe( user.getId() ); + } ); + + it( "can exclude additional attributes when replicating", function() { + var replica = getInstance( "User" ).findOrFail( 1 ).replicate( [ "email" ] ); + + expect( replica.retrieveAttributesData() ).notToHaveKey( "id" ); + expect( replica.retrieveAttributesData() ).notToHaveKey( "email" ); + } ); + it( "can clone a QuickBuilder instance", function() { var userBuilder = getInstance( "User" ).orderBy( "id" ); var userBuilder2 = userBuilder.clone(); diff --git a/tests/specs/integration/BaseEntity/Events/PostReplicateSpec.cfc b/tests/specs/integration/BaseEntity/Events/PostReplicateSpec.cfc new file mode 100644 index 00000000..a6bd5ad6 --- /dev/null +++ b/tests/specs/integration/BaseEntity/Events/PostReplicateSpec.cfc @@ -0,0 +1,51 @@ +component extends="tests.resources.ModuleIntegrationSpec" { + + function beforeAll() { + super.beforeAll(); + controller + .getInterceptorService() + .registerInterceptor( interceptorObject = this, interceptorName = "PostReplicateSpec" ); + } + + function afterAll() { + controller.getInterceptorService().unregister( "PostReplicateSpec" ); + super.afterAll(); + } + + function run() { + describe( "postReplicate spec", function() { + it( "announces a quickPostReplicate interception point", function() { + var original = getInstance( "Song" ).findOrFail( 1 ); + var replica = original.replicate(); + + expect( variables ).toHaveKey( "quickPostReplicateCalled" ); + expect( variables.quickPostReplicateCalled.entity.isLoaded() ).toBeFalse(); + expect( variables.quickPostReplicateCalled.entity.getTitle() ).toBe( replica.getTitle() ); + expect( variables.quickPostReplicateCalled.original.getId() ).toBe( original.getId() ); + structDelete( variables, "quickPostReplicateCalled" ); + } ); + + it( "calls a postReplicate method on the replicated component", function() { + var original = getInstance( "Song" ).findOrFail( 1 ); + var replica = original.replicate(); + + expect( request ).toHaveKey( "postReplicateCalled" ); + expect( request.postReplicateCalled.entity.isLoaded() ).toBeFalse(); + expect( request.postReplicateCalled.entity.getTitle() ).toBe( replica.getTitle() ); + expect( request.postReplicateCalled.original.getId() ).toBe( original.getId() ); + structDelete( request, "postReplicateCalled" ); + } ); + } ); + } + + function quickPostReplicate( + event, + interceptData, + buffer, + rc, + prc + ) { + variables.quickPostReplicateCalled = arguments.interceptData; + } + +}