A hasManyDeep relationship is either a one-to-many or a many-to-many relationship. It is used when you want to access related entities through one or more intermediate entities. For example, you may want to retrieve all the blog posts for a Team:
This would generate the following SQL:
You can generate this same relationship using a Builder syntax:
HasManyDeep relationships can also traverse pivot tables. For instance, a User may have multiple Permissions via a Role entity.
There are two options for constraining a HasManyDeep relationship.
The first option is by using table aliases.
This produces the following SQL:
If you want to use scopes or avoid table aliases, you can use callback functions to constrain the relationship:
// Team.cfc
component extends="quick.models.BaseEntity" accessors="true" {
function posts() {
return hasManyDeep(
relationName = "Post",
through = [ "User" ],
foreignKeys = [
"teamId", // the key on User that refers to Team
"authorId" // the key on Post that refers to User
],
localKeys = [
"id", // the key on Team that identifies Team
"id" // the key on User that identifies User
]
);
}
}SELECT *
FROM `posts`
INNER JOIN `users`
ON `posts`.`authorId` = `users`.`id`
WHERE `users`.`teamId` = ? // team.getId()localKeys
Array<String>
An array of local keys traversing to the relatedEntity.
The length of this array should be one more than the length of through.
The first key of this array would be the column on the parent (current) entity that identifies it, and so forth.
nested
boolean
false
Signals the relationship that it is currently being resolved as part of another hasManyDeep relationship. This is handled by the framework when using a hasManyThrough relationship.
relationMethodName
String
Current Method Name
The method name called to retrieve this relationship. Uses a stack backtrace to determine by default.
relationName
String | Function | QuickBuilder | IRelationship
A mapping name to the final related entity, a function that produces a QuickBuilder or Relationship instance, or a QuickBuilder or Relationship instance.
through
Array<String | Function | QuickBuilder | IRelationship>
The entities to traverse through from the parent (current) entity to get to the relatedEntity.
Each item in the array can be either a mapping name to the final related entity, a function that produces a QuickBuilder or Relationship instance, or a QuickBuilder or Relationship instance.
foreignKeys
Array<String>
An array of foreign keys traversing to the relatedEntity.
The length of this array should be one more than the length of through.
The first key of this array would be the column on the first through entity that refers back to the parent (current) entity, and so forth.
// Team.cfc
component extends="quick.models.BaseEntity" accessors="true" {
function posts() {
return newHasManyDeepBuilder()
.throughEntity( "User", "teamId", "id" )
.toRelated( "Post", "authorId", "id" );
}
}// User.cfc
component extends="quick.models.BaseEntity" accessors="true" {
function permissions() {
return hasManyDeep(
relationName = "Role",
through = [ "roles_users", "Role", "permissions_roles" ],
foreignKeys = [ "userId", "id", "roleId", "id" ],
localKeys = [ "id", "roleId", "id", "permissionId" ]
);
}
}// Team.cfc
component extends="quick.models.BaseEntity" accessors="true" {
function activePosts() {
return hasManyDeep(
relationName = "Post",
through = [ "User AS u" ],
foreignKeys = [
"teamId", // the key on User that refers to Team
"authorId" // the key on Post that refers to User
],
localKeys = [
"id", // the key on Team that identifies Team
"id" // the key on User that identifies User
]
).where( "u.active", 1 );
}
}// Team.cfc
component extends="quick.models.BaseEntity" accessors="true" {
function activePosts() {
return newHasManyDeepBuilder()
.throughEntity( "User AS u", "teamId", "id" )
.toRelated( "Post", "authorId", "id" )
.where( "u.active", 1 );
}
}SELECT *
FROM `posts`
INNER JOIN `users` AS `u`
ON `posts`.`authorId` = `users`.`id`
WHERE `u`.`teamId` = ? // team.getId()
AND `u`.`active` = ? // 1// Team.cfc
component extends="quick.models.BaseEntity" accessors="true" {
function activePosts() {
return hasManyDeep(
relationName = "Post",
through = [ () => newEntity( "User ).active() ],
foreignKeys = [
"teamId", // the key on User that refers to Team
"authorId" // the key on Post that refers to User
],
localKeys = [
"id", // the key on Team that identifies Team
"id" // the key on User that identifies User
]
);
}
}// Team.cfc
component extends="quick.models.BaseEntity" accessors="true" {
function activePosts() {
return newHasManyDeepBuilder()
.throughEntity(
entityName = "User",
foreignKey = "teamId",
localKey = "id",
callback = ( user ) => user.active()
)
.toRelated( "Post", "authorId", "id" )
}
}