Skip to content

Commit

Permalink
feat(Relationships): Relationships can be set using relationship setters
Browse files Browse the repository at this point in the history
Relationship setters take the form of "set" & relationshipName.
It calls the applicable method depending on the relationship type:
associate, saveMany, or sync.
  • Loading branch information
elpete committed May 3, 2019
1 parent 3f30131 commit e1e21a8
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 7 deletions.
36 changes: 30 additions & 6 deletions models/BaseEntity.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,12 @@ component accessors="true" {

function fill( attributes ) {
for ( var key in arguments.attributes ) {
var value = arguments.attributes[ key ];
var rs = tryRelationshipSetter( "set#key#", { "1" = value } );
if ( ! isNull( rs ) ) { continue; }
guardAgainstNonExistentAttribute( key );
variables._data[ retrieveColumnForAlias( key ) ] = arguments.attributes[ key ];
invoke( this, "set#retrieveAliasForColumn( key )#", { 1 = arguments.attributes[ key ] } );
variables._data[ retrieveColumnForAlias( key ) ] = value;
invoke( this, "set#retrieveAliasForColumn( key )#", { 1 = value } );
}
return this;
}
Expand Down Expand Up @@ -340,10 +343,10 @@ component accessors="true" {
}

function newEntity( name ) {
if ( isNull( name ) ) {
if ( isNull( arguments.name ) ) {
return variables._entityCreator.new( this );
}
return variables._wirebox.getInstance( name );
return variables._wirebox.getInstance( arguments.name );
}

function reset() {
Expand Down Expand Up @@ -814,8 +817,10 @@ component accessors="true" {
variables.query = q.retrieveQuery();
return this;
}
var r = tryRelationshipGetter( missingMethodName, missingMethodArguments );
if ( ! isNull( r ) ) { return r; }
var rg = tryRelationshipGetter( missingMethodName, missingMethodArguments );
if ( ! isNull( rg ) ) { return rg; }
var rs = tryRelationshipSetter( missingMethodName, missingMethodArguments );
if ( ! isNull( rs ) ) { return rs; }
if ( relationshipIsNull( missingMethodName ) ) {
return javacast( "null", "" );
}
Expand Down Expand Up @@ -850,6 +855,9 @@ component accessors="true" {
}

var columnName = variables._str.slice( missingMethodName, 4 );
if ( ! hasAttribute( columnName ) ) {
return;
}
assignAttribute( columnName, missingMethodArguments[ 1 ] );
return missingMethodArguments[ 1 ];
}
Expand All @@ -874,6 +882,22 @@ component accessors="true" {
return retrieveRelationship( relationshipName );
}

private function tryRelationshipSetter( missingMethodName, missingMethodArguments ) {
if ( ! variables._str.startsWith( missingMethodName, "set" ) ) {
return;
}

var relationshipName = variables._str.slice( missingMethodName, 4 );

if ( ! hasRelationship( relationshipName ) ) {
return;
}

var relationship = invoke( this, relationshipName );

return relationship.applySetter( argumentCollection = missingMethodArguments );
}

private function relationshipIsNull( name ) {
if ( ! variables._str.startsWith( name, "get" ) ) {
return false;
Expand Down
4 changes: 4 additions & 0 deletions models/Relationships/BelongsTo.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ component extends="quick.models.Relationships.BaseRelationship" {
return entities;
}

function applySetter() {
return associate( argumentCollection = arguments );
}

function associate( entity ) {
var ownerKeyValue = isSimpleValue( entity ) ? entity : entity.retrieveAttribute( variables.ownerKey );
variables.child.forceAssignAttribute( variables.foreignKey, ownerKeyValue );
Expand Down
5 changes: 5 additions & 0 deletions models/Relationships/BelongsToMany.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,15 @@ component accessors="true" extends="quick.models.Relationships.BaseRelationship"
).delete();
}

function applySetter() {
return sync( argumentCollection = arguments );
}

function sync( id ) {
var foreignPivotKeyValue = variables.parent.retrieveAttribute( variables.parentKey );
newPivotStatement().where( variables.foreignPivotKey, "=", foreignPivotKeyValue ).delete();
attach( id );
return variables.parent;
}

function newPivotStatement() {
Expand Down
7 changes: 7 additions & 0 deletions models/Relationships/HasOneOrMany.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ component extends="quick.models.Relationships.BaseRelationship" accessors="true"
return variables.parent.retrieveAttribute( variables.localKey );
}

function applySetter() {
variables.related.updateAll( {
"#variables.foreignKey#" = { "value" = "", "cfsqltype" = "varchar", "null" = true, "nulls" = true }
} );
return saveMany( argumentCollection = arguments );
}

function saveMany( entities ) {
arguments.entities = isArray( entities ) ? entities : [ entities ];
return entities.map( function( entity ) {
Expand Down
27 changes: 27 additions & 0 deletions tests/specs/integration/BaseEntity/Relationships/BelongsToSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,33 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
expect( user.posts().count() ).toBe( 3 );
} );

it( "can set the associated relationship by calling a relationship setter", function() {
var newPost = getInstance( "Post" );
newPost.setBody( "A new post by me!" );
var user = getInstance( "User" ).find( 1 );
newPost.setAuthor( user ).save();
expect( newPost.retrieveAttribute( "user_id" ) ).toBe( user.getId() );
expect( user.posts().count() ).toBe( 3 );
} );

it( "can set the associated relationship by calling a relationship setter with an id", function() {
var newPost = getInstance( "Post" );
newPost.setBody( "A new post by me!" );
var user = getInstance( "User" ).find( 1 );
newPost.setAuthor( user.keyValue() ).save();
expect( newPost.retrieveAttribute( "user_id" ) ).toBe( user.getId() );
expect( user.posts().count() ).toBe( 3 );
} );

it( "can set the associated relationship through fill with an id", function() {
var newPost = getInstance( "Post" ).create( {
"body" = "A new post by me!",
"author" = 1
} );
expect( newPost.retrieveAttribute( "user_id" ) ).toBe( 1 );
expect( newPost.getAuthor().posts().count() ).toBe( 3 );
} );

it( "can disassociate the existing entity", function() {
var post = getInstance( "Post" ).find( 1245 );
expect( post.retrieveAttribute( "user_id" ) ).notToBe( "" );
Expand Down
17 changes: 17 additions & 0 deletions tests/specs/integration/BaseEntity/Relationships/HasManySpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
expect( posts[ 2 ].getAuthor().getId() ).toBe( user.getId() );
} );

it( "can sync the array of ids using a relationship setter", function() {
var newPost = getInstance( "Post" );
newPost.setBody( "A new post by me!" );
newPost.save();
expect( newPost.isLoaded() ).toBeTrue();

var user = getInstance( "User" ).find( 1 );
expect( user.getPosts() ).toBeArray();
expect( user.getPosts() ).toHaveLength( 2 );
var posts = user.setPosts( newPost );

var posts = user.fresh().getPosts();
expect( posts ).toBeArray();
expect( posts ).toHaveLength( 1 );
expect( posts[ 1 ].keyValue() ).toBe( newPost.keyValue() );
} );

it( "can create new related entities directly", function() {
var user = getInstance( "User" ).find( 1 );
expect( user.getPosts() ).toHaveLength( 2 );
Expand Down
33 changes: 32 additions & 1 deletion tests/specs/integration/BaseEntity/SaveSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
expect( newUserAgain.getLastName() ).toBe( "User2" );
expect( newUserAgain.getEmail() ).toBe( "test2@test.com" );
} );


it( "retrieves the generated key when saving a new record", function() {
var newUser = getInstance( "User" );
Expand Down Expand Up @@ -245,6 +245,37 @@ component extends="tests.resources.ModuleIntegrationSpec" appMapping="/app" {
expect( post.getTags().toArray() ).toHaveLength( 3 );
expect( post.getTags().map( function( tag ) { return tag.keyValue(); } ).toArray() ).toBe( tagIds );
} );

it( "sets the related ids equal to the list passed in using a relationship setter", function() {
var newTagA = getInstance( "Tag" );
newTagA.setName( "miscellaneous" );
newTagA.save();

var newTagB = getInstance( "Tag" );
newTagB.setName( "other" );
newTagB.save();

var post = getInstance( "Post" ).find( 1245 );

expect( post.getTags().toArray() ).toBeArray();
expect( post.getTags().toArray() ).toHaveLength( 2 );
var existingTags = post.getTags().toArray();

var tagsToSync = [ existingTags[ 1 ], newTagA.getId(), newTagB ];
var tagIds = [
existingTags[ 1 ].keyValue(),
newTagA.keyValue(),
newTagB.keyValue()
];

post.setTags( [ existingTags[ 1 ], newTagA.getId(), newTagB ] );

post.refresh();

expect( post.getTags().toArray() ).toBeArray();
expect( post.getTags().toArray() ).toHaveLength( 3 );
expect( post.getTags().map( function( tag ) { return tag.keyValue(); } ).toArray() ).toBe( tagIds );
} );
} );
} );
} );
Expand Down

0 comments on commit e1e21a8

Please sign in to comment.