Skip to content

Commit

Permalink
New hasManyDeep and hasManyDeepBuilder relationships.
Browse files Browse the repository at this point in the history
`hasManyThrough` needs some work to be more performant and produce expected
queries. `hasManyDeep` is a ground up rewrite alongside `hasManyThrough`.
Eventually we will add a `belongsToDeep` and hopefully even patch
`hasManyThrough` and `belongsToThrough` through the new relationships.
  • Loading branch information
elpete committed Mar 12, 2024
1 parent 026d7e0 commit 3a6c7ba
Show file tree
Hide file tree
Showing 22 changed files with 914 additions and 42 deletions.
65 changes: 65 additions & 0 deletions models/BaseEntity.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -2020,6 +2020,71 @@ component accessors="true" {
);
}

public HasManyDeep function hasManyDeep(
required string relationName,
required array through,
required array foreignKeys,
required array localKeys,
string relationMethodName
) {
param arguments.relationMethodName = lCase( callStackGet()[ 2 ][ "Function" ] );

guardAgainstNotLoaded(
"This instance is not loaded so it cannot access the [#arguments.relationMethodName#] relationship. Either load the entity from the database using a query executor (like `first`) or base your query off of the [#arguments.relationName#] entity directly and use the `has` or `whereHas` methods to constrain it based on data in [#entityName()#]."
);

var throughParents = arguments.through.map( function( throughEntityName ) {
var parts = throughEntityName.split( "[Aa][Ss]" );
if ( variables._wirebox.containsInstance( parts[ 1 ] ) ) {
var entity = variables._wirebox.getInstance( parts[ 1 ] );
if ( arrayLen( parts ) > 1 ) {
entity.withAlias( parts[ 2 ] );
}
return entity;
} else {
// turn parts into a CFML array
var pivotTable = variables._wirebox.getInstance( "PivotTable@quick" )
pivotTable.setTable( parts[ 1 ] );
if ( arrayLen( parts ) > 1 ) {
pivotTable.withAlias( parts[ 2 ] );
}
return pivotTable;
}
} );


var parts = arguments.relationName.split( "[Aa][Ss]" );
var related = variables._wirebox.getInstance( trim( parts[ 1 ] ) );
if ( arrayLen( parts ) > 1 ) {
related.withAlias( trim( parts[ 2 ] ) );
}

return variables._wirebox.getInstance(
name = "HasManyDeep@quick",
initArguments = {
"related" : related,
"relationName" : arguments.relationName,
"relationMethodName" : arguments.relationMethodName,
"parent" : this,
"throughParents" : throughParents,
"foreignKeys" : arguments.foreignKeys,
"localKeys" : arguments.localKeys,
"withConstraints" : !variables._withoutRelationshipConstraints
}
);
}

private HasManyDeepBuilder function newHasManyDeepBuilder( string relationMethodName ) {
param arguments.relationMethodName = lCase( callStackGet()[ 2 ][ "Function" ] );
return variables._wirebox.getInstance(
"HasManyDeepBuilder@quick",
{
"parent" : this,
"relationMethodName" : arguments.relationMethodName
}
);
}

/*=======================================
= QB Utilities =
=======================================*/
Expand Down
42 changes: 32 additions & 10 deletions models/Relationships/BaseRelationship.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* @doc_abstract true
*/
component accessors="true" {
component accessors="true" implements="IRelationship" {

/**
* The WireBox Injector.
Expand Down Expand Up @@ -92,6 +92,13 @@ component accessors="true" {
return this;
}

public void function addConstraints() {
throw(
type = "NotImplemented",
message = "The `addConstraints` method must be implemented in the concrete relationship."
);
}

/**
* Sets the relation method name for this relationship.
*
Expand Down Expand Up @@ -187,6 +194,20 @@ component accessors="true" {
return variables.getResults();
}

public any function getResults() {
throw(
type = "NotImplemented",
message = "The `getResults` method must be implemented in the concrete relationship."
);
}

public array function getQualifiedForeignKeyNames( any builder = variables.relationshipBuilder ) {
throw(
type = "NotImplemented",
message = "The `getQualifiedForeignKeyNames` method must be implemented in the concrete relationship."
);
}

/**
* Retrieves the values of the key from each entity passed.
*
Expand Down Expand Up @@ -242,14 +263,14 @@ component accessors="true" {
*
* @return quick.models.BaseEntity | qb.models.Query.QueryBuilder
*/
public any function addCompareConstraints( any base = variables.relationshipBuilder ) {
public any function addCompareConstraints( any base = variables.relationshipBuilder, any nested ) {
return arguments.base
.select( variables.relationshipBuilder.raw( 1 ) )
.where( function( q ) {
arrayZipEach(
[
getExistanceLocalKeys(),
getExistenceCompareKeys()
getExistenceLocalKeys( base ),
getExistenceCompareKeys( base )
],
function( qualifiedLocalKey, existenceCompareKey ) {
q.whereColumn( qualifiedLocalKey, existenceCompareKey );
Expand All @@ -270,7 +291,7 @@ component accessors="true" {
* @doc_generic String
* @return [String]
*/
public array function getQualifiedLocalKeys() {
public array function getQualifiedLocalKeys( any builder = variables.relationshipBuilder ) {
return variables.parent.retrieveQualifiedKeyNames();
}

Expand All @@ -280,8 +301,8 @@ component accessors="true" {
* @doc_generic String
* @return [String]
*/
public array function getExistanceLocalKeys() {
return getQualifiedLocalKeys();
public array function getExistenceLocalKeys( any builder = variables.relationshipBuilder ) {
return getQualifiedLocalKeys( arguments.builder );
}

/**
Expand All @@ -290,8 +311,8 @@ component accessors="true" {
* @doc_generic String
* @return [String]
*/
public array function getExistenceCompareKeys() {
return getQualifiedForeignKeyNames();
public array function getExistenceCompareKeys( any builder = variables.relationshipBuilder ) {
return getQualifiedForeignKeyNames( arguments.builder );
}

/**
Expand Down Expand Up @@ -329,7 +350,7 @@ component accessors="true" {
*
* @return qb.models.Query.QueryBuilder
*/
public any function retrieveQuery() {
public QueryBuilder function retrieveQuery() {
return variables.relationshipBuilder.retrieveQuery();
}

Expand Down Expand Up @@ -435,6 +456,7 @@ component accessors="true" {
var lengths = arguments.arrays.map( function( arr ) {
return arr.len();
} );

if ( unique( lengths ).len() > 1 ) {
throw(
type = "ArrayZipLengthMismatch",
Expand Down
4 changes: 2 additions & 2 deletions models/Relationships/BelongsTo.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ component extends="quick.models.Relationships.BaseRelationship" accessors="true"
* @doc_generic String
* @return [String]
*/
public array function getQualifiedLocalKeys() {
public array function getQualifiedLocalKeys( any builder = variables.relationshipBuilder ) {
return variables.localKeys.map( function( localKey ) {
return variables.related.qualifyColumn( localKey );
} );
Expand All @@ -360,7 +360,7 @@ component extends="quick.models.Relationships.BaseRelationship" accessors="true"
* @doc_generic String
* @return [String]
*/
public array function getExistenceCompareKeys() {
public array function getExistenceCompareKeys( any builder = variables.relationshipBuilder ) {
return variables.foreignKeys.map( function( foreignKey ) {
return variables.child.qualifyColumn( foreignKey );
} );
Expand Down
6 changes: 3 additions & 3 deletions models/Relationships/BelongsToMany.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ component extends="quick.models.Relationships.BaseRelationship" accessors="true"
} );
}

function nestCompareConstraints( base, nested ) {
function nestCompareConstraints( required any base, required any nested ) {
return structKeyExists( arguments.nested, "retrieveQuery" ) ? arguments.nested.retrieveQuery() : arguments.nested;
}

Expand All @@ -566,8 +566,8 @@ component extends="quick.models.Relationships.BaseRelationship" accessors="true"
* @doc_generic String
* @return [String]
*/
public array function getQualifiedForeignKeyNames() {
return getQualifiedForeignPivotKeyNames();
public array function getQualifiedForeignKeyNames( any builder = variables.relationshipBuilder ) {
return getQualifiedForeignPivotKeyNames( arguments.builder );
}

/**
Expand Down
2 changes: 1 addition & 1 deletion models/Relationships/BelongsToThrough.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ component extends="quick.models.Relationships.BaseRelationship" {
*
* @return quick.models.BaseEntity | qb.models.Query.QueryBuilder
*/
public any function addCompareConstraints( any base = variables.related ) {
public any function addCompareConstraints( any base = variables.related, any nested ) {
return tap( arguments.base.select(), function( q ) {
performJoin( q );
q.where( function( q2 ) {
Expand Down
88 changes: 88 additions & 0 deletions models/Relationships/Builders/HasManyDeepBuilder.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
component accessors="true" {

property name="relationName" type="string";
property name="through" type="array";
property name="foreignKeys" type="array";
property name="localKeys" type="array";

public HasManyDeepBuilder function init( required any parent, required string relationMethodName ) {
variables.parent = arguments.parent;
variables.relationMethodName = arguments.relationMethodName;

variables.through = [];
variables.foreignKeys = [];
variables.localKeys = [];

return this;
}

public HasManyDeepBuilder function throughEntity(
required string entityName,
required any foreignKey,
required any localKey
) {
variables.through.append( arguments.entityName );
variables.foreignKeys.append( arguments.foreignKey );
variables.localKeys.append( arguments.localKey );
return this;
}

public HasManyDeepBuilder function throughPivotTable(
required string tableName,
required any foreignKey,
required any localKey
) {
variables.through.append( arguments.tableName );
variables.foreignKeys.append( arguments.foreignKey );
variables.localKeys.append( arguments.localKey );
return this;
}

public HasManyDeepBuilder function throughPolymorphicEntity(
required string entityName,
required string type,
required any foreignKey,
required any localKey
) {
variables.through.append( arguments.entityName );
variables.foreignKeys.append( [ arguments.type, arguments.foreignKey ] );
variables.localKeys.append( arguments.localKey );
return this;
}

public HasManyDeep function toRelated(
required string relationName,
required any foreignKey,
required any localKey
) {
variables.foreignKeys.append( arguments.foreignKey );
variables.localKeys.append( arguments.localKey );

return variables.parent.hasManyDeep(
relationName = arguments.relationName,
through = variables.through,
foreignKeys = variables.foreignKeys,
localKeys = variables.localKeys,
relationMethodName = variables.relationMethodName
);
}

public HasManyDeep function toPolymorphicRelated(
required string relationName,
required string type,
required any foreignKey,
required any localKey
) {
variables.foreignKeys.append( [ arguments.type, arguments.foreignKey ] );
variables.localKeys.append( arguments.localKey );

return variables.parent.hasManyDeep(
relationName = arguments.relationName,
through = variables.through,
foreignKeys = variables.foreignKeys,
localKeys = variables.localKeys,
relationMethodName = variables.relationMethodName
);
}

}
Loading

0 comments on commit 3a6c7ba

Please sign in to comment.