Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
A hasOne relationship is a "one-to-one" relationship. For instance, a User entity might have an UserProfile entity attached to it.
// User.cfc
component extends="quick.models.BaseEntity" {
function profile() {
return hasOne( "UserProfile" );
}
}The first value passed to hasOne is a WireBox mapping to the related entity.
Quick determines the foreign key of the relationship based on the entity name and key values. In this case, the UserProfile entity is assumed to have a userId foreign key. You can override this by passing a foreign key in as the second argument:
If your parent entity does not use id as its primary key, or you wish to join the child entity to a different column, you may pass a third argument to the belongsTo method specifying your parent table's custom key.
The inverse of hasOne is . It is important to choose the right relationship for your database structure. hasOne assumes that the related model has the foreign key for the relationship.
return hasOne("UserProfile", "FK_userID");return belongsTo("UserProfile", "FK_userID", "profile_id");// UserProfile.cfc
component extends="quick.models.BaseEntity" {
function user() {
return belongsTo( "User" );
}
}Relationship Types
Relationships are the heart of any ORM engine. They let you interact with relational database tables in an object-oriented way.
Quick's relationship engine provides readable relationship types, extendible relations at runtime, eager loading, and much more.
Start by checking out the different relationship types.
A polymorphicHasMany relationship is a one-to-many relationship. This relationship is used when an entity can belong to multiple types of entities. The classic example for this type of relationship is Posts, Videos, and Comments.
// Post.cfc
component extends="quick.models.BaseEntity" {
function comments() {
return polymorphicHasMany( "Comment", "commentable" );
}
}// Video.cfc
component extends="quick.models.BaseEntity" {
function comments() {
return polymorphicHasMany( "Comment", "commentable" );
}
}The first value passed to polymophicHasMany is a WireBox mapping to the related entity.
The second value is a prefix for the polymorphic type. A common convention where is to add able to the end of the entity name, though this is not automatically done. In our example, this prefix is commentable. This tells quick to look for a commentable_type and a commentable_id column in our Comment entity. It stores our entity's mapping as the _type and our entity's primary key value as the _id.
The inverse of polymophicHasMany is polymorphicBelongsTo.
// Comment.cfc
component extends="quick.models.BaseEntity" {
function post() {
return polymorphicBelongsTo( "commentable" );
}
}A belongsTo relationship is a many-to-one relationship. For instance, a Post may belong to a User.
The first value passed to belongsTo is a WireBox mapping to the related entity.
Quick determines the foreign key of the relationship based on the entity name and key values. In this case, the Post entity is assumed to have a userId foreign key. You can override this by passing a foreign key in as the second argument:
If your parent entity does not use id as its primary key, or you wish to join the child entity to a different column, you may pass a third argument to the belongsTo method specifying your parent table's custom key.
The inverse of belongsTo is or .
To update a belongsTo relationship, use the associate method. associate takes the entity to associate as the only argument.
Note:
associatedoes not automatically save the entity. Make sure to callsavewhen you are ready to persist your changes to the database.
To remove a belongsTo relationship, use the dissociate method.
Note:
dissociatedoes not automatically save the entity. Make sure to callsavewhen you are ready to persist your changes to the database.
You can also influence the associated entities by calling "set" & relationshipName and passing in an entity or key value.
After executing this code, the post would be updated in the database to be associated with the user with an id of 1.
// Post.cfc
component extends="quick.models.BaseEntity" {
function user() {
return belongsTo( "User" );
}
}return belongsTo("User", "FK_userID");return belongsTo("User", "FK_userID", "relatedPostId");// User.cfc
component extends="quick.models.BaseEntity" {
function posts() {
return hasMany( "Post" );
}
function latestPost() {
// remember, relationships are just queries!
return hasOne( "Post" ).orderBy( "createdDate", "desc" );
}
}var post = getInstance("Post").findOrFail(1);
var user = getInstance("User").findOrFail(1);
post.user().associate(user);
post.save();var post = getInstance("Post").findOrFail(1);
post.user().dissociate();
post.save();var post = getInstance( "Post" ).first();
post.setAuthor( 1 );Relationships can be used in two ways.
The first is as a getter. Calling user.getPosts() will execute the relationship, cache the result, and return it.
var posts = user.getPosts();The second is as a relationship. Calling user.posts() returns a Relationship instance to retrieve the posts that can be further constrained. A Relationship is backed by qb as well, so feel free to call any qb method to further constrain the relationship.
var newestPosts = user
.posts()
.orderBy( "publishedDate", "desc" )
.get();You can also call the other Quick fetch methods: first, firstOrFail, find, and findOrFail are all supported. This is especially useful to constrain the entities available to a user by using the user's relationships:
A belongsToMany relationship is a many-to-many relationship. For instance, a User may have multiple Permissions while a Permission can belong to multiple Users.
The first value passed to belongsToMany is a WireBox mapping to the related entity.
belongsToMany makes some assumptions about your table structure. To support a many-to-many relationship, you need a pivot table. This is, at its simplest, a table with each of the foreign keys as columns.
// This will only find posts the user has written.
var post = user.posts().findOrFail( rc.id );_) to create the new pivot table name. If you want to override this convention, you can do so by passing the desired table name as the second parameter or the table parameter.Quick determines the foreign key of the relationship based on the entity name and key values. In this case, the User entity is assumed to have a userId foreign key and the Permission entity a permissionId foreign key. You can override this by passing a foreignKey in as the third argument and a relatedKey as the fourth argument:
Finally, if you are not joining on the primary keys of the current entity or the related entity, you can specify those keys using the last two parameters:
The inverse of belongsToMany is also belongsToMany. The foreignKey and relatedKey arguments are swapped on the inverse side of the relationship.
If you find yourself needing to interact with the pivot table (permissions_users) in the example above, you can create an intermediate entity, like UserPermission. You will still be able to access the end of the relationship chain using the hasManyThrough relationship type.
Use the attach method to relate two belongsToMany entities together. attach can take a single id, a single entity, or an array of ids or entities (even mixed and matched) to associate.
Use the detach method to remove an existing entity from a belongsToMany relationship. detatch can also take a single id, a single entity, or an array of ids or entities (even mixed and matched) to remove.
Sometimes you just want the related entities to be a list you give it. For these situations, use the sync method.
Now, no matter what relationships existed before, this Post will only have three tags associated with it.
You can also influence the associated entities by calling "set" & relationshipName and passing in an entity or key value.
This code calls sync on the relationship. After executing this code, the post would be updated in the database to be associated with the tags passed in (4, 12, and 2). Any tags that were previously associated with this post would no longer be and only the tags passed in would be associated now.
// User.cfc
component extends="quick.models.BaseEntity" {
function permissions() {
return belongsToMany( "Permission" );
}
}// Permission.cfc
component extends="quick.models.BaseEntity" {
function users() {
return belongsToMany( "User" );
}
}permissions_users
- permissionId
- userId// User.cfc
component extends="quick.models.BaseEntity" {
function permissions() {
return belongsToMany( "Permission", "user_permission_map" );
}
}return belongsToMany(
"Permission",
"user_permission_map",
"FK_UserId",
"FK_PermissionID"
);return belongsToMany(
"Permission",
"user_permission_map",
"FK_UserId",
"FK_PermissionID",
"user_id",
"permission_id"
);// Permission.cfc
component extends="quick.models.BaseEntity" {
function user() {
belongsToMany( "User", "user_permission_map", "FK_PermissionID", "FK_UserId" );
}
}// User.cfc
component extends="quick.models.BaseEntity" {
function userPermissions() {
return hasMany( "UserPermission" );
}
function permissions() {
return hasManyThrough( "Permission", "UserPermission" );
}
}var post = getInstance("Post").findOrFail(1);
var tag = getInstance("Tag").create("miscellaneous");
// pass an id
post.tags().attach(tag.getId());
// or pass an entity
post.tags().attach(tag);var post = getInstance("Post").findOrFail(1);
var tag = getInstance("Tag").create("miscellaneous");
// pass an id
post.tags().detach(tag.getId());
// or pass an entity
post.tags().detach(tag);var post = getInstance("Post").findOrFail(1);
post.tags().sync([2, 3, 6]);var someTag = getInstance( "Tag" ).findOrFail( 2 );
var post = getInstance( "Post" ).first();
post.setTags( [ 4, 12, someTag );A hasManyThrough relationship is a many-to-many relationship. It is used when you want to access a related entity through another entity. The most common example for this is through a pivot table. For instance, a User may have multiple Permissions via a UserPermission entity. This allows you to store additional data on the UserPermission entity, like a createdDate .
// User.cfc
component extends="quick.models.BaseEntity" {
function permissions() {
return hasManyThrough( "Permission" );
}
}// Permission.cfc
component extends="quick.models.BaseEntity" {
function users() {
return hasManyThrough( "User" );
}
}The first value passed to hasManyThrough is a WireBox mapping to the related entity.
The second value passed is a WireBox mapping to the intermediate entity.
Quick determines the foreign key of the relationship based on the entity name and key values. In this case, the Permission entity is assumed to have a permissionId foreign key. You can override this by passing a foreign key in as the third argument:
The secondKey is also determined by Quick. It is the foreign key of the current entity for the intermediate entity's table. In our example, this would be userId, since User is our entity and it is for the UserPermissions table. You can override this by passing in the secondKey as the fourth argument.
Lastly, the localKey and secondLocalKey are the primary keys of the entity and the intermediate entities. Usually this is just id. You can override these as the fifth and sixth argument.
The inverse of hasManyThrough is also hasManyThrough. A note that the intermediate entity would use belongsTo relationships to link back to each side of the hasManyThrough relationship. These relationships are not needed to use a hasManyThrough relationship.
A polymorphicBelongsTo relationship is a many-to-one relationship. This relationship is used when an entity can belong to multiple types of entities. The classic example for this type of relationship is Posts, Videos, and Comments. For instance, a Comment may belong to a Post or a Video.
// Comment.cfc
component extends="quick.models.BaseEntity" {
function post() {
return polymorphicBelongsTo( "commentable" );
}
}The only value passed to polymorphicBelongsTo is a prefix for the polymorphic type. A common convention where is to add able to the end of the entity name, though this is not automatically done. In our example, this prefix is commentable. This tells quick to look for a commentable_type and a commentable_id column in our Comment entity. It stores our entity's mapping as the _type and our entity's primary key value as the _id.
When retrieving a polymorphicBelongsTo relationship the _id is used to retrieve a _type from the database.
The inverse of polymorphicBelongsTo is also polymorphicHasMany. It is important to choose the right relationship for your database structure. hasOne assumes that the related model has the foreign key for the relationship.
// UserPermission.cfc
component extends="quick.models.BaseEntity" {
function user() {
return belongsTo( "User" );
}
function permission() {
return belongsTo( "Permission" );
}
}return hasManyThrough("Permission", "UserPermission", "FK_permissionID");return hasManyThrough(
"Permission",
"UserPermission",
"FK_permissionID",
"FK_userID"
);// Post.cfc
component extends="quick.models.BaseEntity" {
function comments() {
return polymorphicHasMany( "Comment", "commentable" );
}
}// Video.cfc
component extends="quick.models.BaseEntity" {
function comments() {
return polymorphicHasMany( "Comment", "commentable" );
}
}return hasManyThrough(
relationName = "Permission",
intermediate = "UserPermission",
firstKey = "FK_permissionID", // foreign key on the UserPermission table
secondKey = "FK_userID", // foreign key on the Permission table
localKey = "userID", // local key on the owning entity table
secondLocalKey = "id" // local key on the UserPermission table
);A hasMany relationship is a one-to-many relationship. For instance, a User may have multiple Posts.
The first value passed to hasMany is a WireBox mapping to the related entity.
Quick determines the foreign key of the relationship based on the entity name and key values. In this case, the Post entity is assumed to have a userId foreign key. You can override this by passing a foreign key in as the second argument:
If your parent entity does not use id as its primary key, or you wish to join the child entity to a different column, you may pass a third argument to the belongsTo method specifying your parent table's custom key.
The inverse of hasMany is also .
There are two ways to add an entity to a hasMany relationship. Both mirror the for entities.
You can call the save method on the relationship passing in an entity to relate.
This will add the User entity's id as a foreign key in the Post and save the Post to the database.
Note: the
savemethod is called on thepostsrelationship, not thegetPostscollection.
You can also add many entities in a hasMany relationship by calling saveMany. This method takes an array of key values or entities and will associate each of them with the base entity.
Use the create method to create and save a related entity directly through the relationship.
This example will have the same effect as the previous example.
Removing a hasMany relationship is handled in two ways: either by using the dissociate method on the side of the relationship or by deleting the side of the relationship.
You can also influence the associated entities by calling "set" & relationshipName and passing in an array of entities or key values.
After running this code, this user would only have two posts, the posts with ids 2 and 4. Any other posts would now be disassociated with this user. Likely your database will be guarding against creating these orphan records. Admittedly, this method is not as likely to be used as the others, but it does exist if it solves your use case.
// User.cfc
component extends="quick.models.BaseEntity" {
function posts() {
return hasMany( "Post" );
}
}return hasMany("Post", "FK_userID");return hasMany("Post", "FK_userID", "relatedPostId");// Post.cfc
component extends="quick.models.BaseEntity" {
function user() {
return belongsTo( "User" );
}
}var post = getInstance( "Post" ).create( {
"title" = "My Post",
"body" = "Hello, world!"
} );
var user = getInstance( "User" ).findOrFail( 1 );
user.posts().save( post );
// OR use the keyValue
user.posts().save( post.keyValue() );var user = getInstance( "User" ).findOrFail( 1 );
user.posts().create( {
"title" = "My Post",
"body" = "Hello, world!"
} );var postA = getInstance( "Post" ).findOrFail( 2 );
user.setPosts( [ postA, 4 ] );Let's imagine a scenario where you are displaying a list of posts. You fetch the posts:
And start looping through them:
When you visit the page, though, you notice it takes a while to load. You take a look at your SQL console and you've executed 26 queries for this one page! What?!?
Turns out that each time you loop through a post to display its author's username you are executing a SQL query to retreive that author. With 25 posts this becomes 25 SQL queries plus one initial query to get the posts. This is where the N+1 problem gets its name.
So what is the solution? Eager Loading.
Eager Loading means to load all the needed users for the posts in one query rather than separate queries and then stitch the relationships together. With Quick you can do this with one method call.
You can eager load a relationship with the with method call.
with takes one parameter, the name of the relationship to load. Note that this is the name of the function, not the entity name. For example:
To eager load the User in the snippet above you would call pass author to the with method.
For this operation, only two queries will be executed:
Quick will then stitch these relationships together so when you call post.getAuthor() it will use the fetched relationship value instead of going to the database.
You can eager load nested relationships using dot notation. Each segment must be a valid relationship name.
You can eager load multiple relationships by passing an array of relation names to with or by calling with multiple times.
In most cases when you want to constrain an eager loaded relationship, the better approach is to create a new relationship.
You can eager load either option.
Occassionally that decision needs to be dynamic. For example, maybe you only want to eager load the posts created within a timeframe defined by a user. To do this, pass a struct instead of a string to the with function. The key should be the name of the relationship and the value should be a function. This function will accept the related entity as its only argument. Here is an example:
If you need to load nested relationships with constraints you can call with in your constraint callback to continue eager loading relationships.
Finally, you can postpone eager loading until needed by using the load method on QuickCollection. load has the same function signature as with. QuickCollection is the object returned for all Quick queries that return more than one record. Read more about it in .
prc.posts = getInstance( "Post" ).limit( 25 ).get():<cfoutput>
<h1>Posts</h1>
<ul>
<cfloop array="#prc.posts#" item="post">
<li>#post.getTitle()# by #post.getAuthor().getUsername()#</li>
</cfloop>
</ul>
</cfoutput>prc.posts = getInstance( "Post" )
.with( "author" )
.limit( 25 )
.get();// Post.cfc
component extends="quick.models.BaseEntity" {
function author() {
return belongsTo( "User" );
}
}getInstance( "Post" ).with( "author" ).get();SELECT * FROM `posts` LIMIT 25
SELECT * FROM `users` WHERE `id` IN (1, 2, 3, 4, 5, 6, ...)// User.cfc
component extends="quick.models.BaseEntity" {
function country() {
return belongsTo( "User" );
}
}getInstance( "Post" ).with( "author.country" );getInstance( "Post" ).with( [ "author.country", "tags" ] );// User.cfc
component {
function posts() {
return hasMany( "Post" );
}
function publishedPosts() {
return hasMany( "Post" ).published(); // published is a query scope on Post
}
}getInstance( "User" ).with( "posts" ).get();
getInstance( "User" ).with( "publishedPosts" ).get();getInstance( "User" ).with( { "posts" = function( query ) {
} } ).latest().get();getInstance( "User" ).with( { "posts" = function( q1 ) {
return q1
.whereBetween( "published_date", rc.startDate, rc.endDate )
.with( { "comments" = function( q2 ) {
return q2.where( "body", "like", rc.search );
} } );
} } ).latest().get();