Only this pageAll pages
Powered by GitBook
1 of 25

1.3.0

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Retrieving Relationships

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();

Relationships

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 .

Retrieving Entities

Once you have an entity and its associated database table you can start retrieving data from your database.

You start every interaction with Quick with an instance of an entity. The easiest way to do this is using WireBox. getInstance is available in all handlers by default. WireBox can easily be injected in to any other class you need using inject="wirebox".

Quick is backed by qb, a CFML Query Builder. With this in mind, think of retrieving records for your entities like interacting with qb. For example:

In addition to using for you can utilize the each function on QuickCollection. For example:

You can add constraints to query just the same as you would using qb directly:

For more information on what is possible with qb, check out the .

Collections

Queries that return more than one record return a QuickCollection, a type of CFCollection. Read more about .

Aggregates

Calling qb's aggregate methods (count, max, etc.) will return the appropriate value instead of an entity or collection of entities.

Custom Quick Retrieval Methods

There are a few custom retrieval methods for Quick:

all

Retrieves all the records for an entity. Calling all will ignore any constraints on the query.

findOrFail & firstOrFail

These two methods will throw a EntityNotFound exception if the query returns no results.

The findOrFail method should be used in place of find, passing an id in to retrieve.

The firstOrFail method should be used in place of first, being called after constraining a query.

relationship types
var users = getInstance( "User" ).all();

// users is a QuickCollection.  To get an array,
// first call `get` or `toArray`
for ( var user in users.get() ) {
    writeOutput( user.getUsername() );
}
var users = getInstance( "User" ).all();

prc.users.each( function( user ) {
    writeOutput( user.getUsername() );
} );
var users = getInstance( "User" )
    .where( "active", 1 )
    .orderBy( "username", "desc" )
    .limit( 10 )
    .get();
qb documentation
collections here

hasMany

Defining

A hasMany relationship is a one-to-many relationship. For instance, a User may have multiple Posts.

// User.cfc
component extends="quick.models.BaseEntity" {

    function posts() {
       return hasMany( "Post" );
    }

}

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:

return hasMany( "Post", "FK_userID" );

The inverse of hasMany is also belongsTo.

// Post.cfc
component extends="quick.models.BaseEntity" {

    function user() {
        return belongsTo( "User" );
    }

}

Inserting & Updating

There are two ways to add an entity to a hasMany relationship. Both mirror the insert API for entities.

save

You can call the save method on the relationship passing in an entity to relate.

var post = getInstance( "Post" ).fill( {
    "title" = "My Post",
    "body" = "Hello, world!"
} );

var user = getInstance( "User" ).findOrFail( 1 );

user.posts().save( post );

This will add the User entity's id as a foreign key in the Post and save the Post to the database.

Note: the save method is called on the posts relationship, not the getPosts collection.

create

Use the create method to create and save a related entity directly through the relationship.

var user = getInstance( "User" ).findOrFail( 1 );

user.posts().create( {
    "title" = "My Post",
    "body" = "Hello, world!"
} );

This example will have the same effect as the previous example.

Removing

Removing a hasMany relationship is handled in two ways: either by using the dissociate method on the belongsTo side of the relationship or by deleting the belongsTo side of the relationship.

Collections

All queries that potentially return more than one record using Quick are returned using a QuickCollection. QuickCollection is a specialized version of CFCollection. It is a component that smooths over the various CFML engines to provide an extendible, reliable array wrapper with functional programming methods. You may be familiar with methods like map (ArrayMap), filter (ArrayFilter), or reduce (ArrayReduce). These methods work in every CFML engine with CFCollection.

Collections are more powerful than plain arrays. There are many methods that can make your work easier. For instance, let's say you needed to group each active user by the first letter of their username in a list.

var users = getInstance( "User" ).all();

users
    .filter( function( user ) {
        return user.getActive();
    } )
    .pluck( "username" )
    .groupBy( function( username ) {
        return left( username, 1 );
    } );

So powerful! We think you'll love it.

load

Additionally, QuickCollection includes a load method. load lets you eager load a relationship after executing the initial query.

var posts = getInstance( "Post" ).all();

if ( someCondition ) {
    posts.load( "user" );
}

This is the same as if you had initially executed:

getInstance( "Post" ).with( "user" ).all()

$renderData

QuickCollection includes a $renderData method that lets you return a QuickCollection directly from your handler and translates the results and the entities within to a serialized version. Check out more about it in the Serialization chapter.

Debugging

Hook in to qb's interception points.

polymorphicBelongsTo

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.

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.

// Comment.cfc
component extends="quick.models.BaseEntity" {

    function post() {
        return polymorphicBelongsTo( "commentable" );
    }

}
// 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" );
    }

}

belongsTo

A belongsTo relationship is a many-to-one relationship. For instance, a Post may belong to a User.

// Post.cfc
component extends="quick.models.BaseEntity" {

    function user() {
       return belongsTo( "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:

return hasMany( "Post", "FK_userID" );

The inverse of belongsTo is hasMany or hasOne.

// 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" );
    }

}

Updating

To update a belongsTo relationship, use the associate method. associate takes the entity to associate as the only argument.

var post = getInstance( "Post" ).findOrFail( 1 );

var user = getInstance( "User" ).findOrFail( 1 );

post.user().associate( user );

post.save();

Note: associate does not automatically save the entity. Make sure to call save when you are ready to persist your changes to the database.

Removing

To remove a belongsTo relationship, use the dissociate method.

var post = getInstance( "Post" ).findOrFail( 1 );

post.user().dissociate()

post.save();

Note: dissociate does not automatically save the entity. Make sure to call save when you are ready to persist your changes to the database.

Defining An Entity

To get started with Quick, you need an entity. There are two ways to define an entity.

The first way is to extend quick.models.BaseEntity.

// User.cfc
component extends="quick.models.BaseEntity" {}

The second way, in a ColdBox application, is to annotate your component with the quick annotation.

// User.cfc
component quick {}

This will use WireBox's Virtual Inheritance to accomplish the same as extending the BaseEntity.

That's all that is needed to get started with Quick. There are a few defaults of Quick worth mentioning here.

Tables

We don't need to tell Quick what table name to use for our entity. By default, Quick uses the pluralized name of the component for the table name. That means for our User entity Quick will assume the table name is users. You can override this by specifying a table metadata attribute on the component.

// User.cfc
component table="my_users" extends="quick.models.BaseEntity" {}

Primary Key

By default, Quick assumes a primary key of id. The name of this key can be configured by setting variables.key in your component.

// User.cfc
component extends="quick.models.BaseEntity" {

    variables.key = "user_id";

}

Quick also assumes a key type that is auto-incrementing. If you would like a different key type, inject it as the `keyType` property. Quick ships with a `UUID` type that you can use as well.

// User.cfc
component extends="quick.models.BaseEntity" {

    property name="keyType" inject="UUID@quick" persistent="false";

}

keyType can be any class that adheres to the keyType interface, so feel free to create your own and distribute them via ForgeBox.

interface displayname="KeyType" {

    /**
    * Called to handle any tasks before inserting into the database.
    * Recieves the entity as the only argument.
    */
    public void function preInsert( required entity );

    /**
    * Called to handle any tasks after inserting into the database.
    * Recieves the entity and the queryExecute result as arguments.
    */
    public void function postInsert( required entity, required struct result );

}

Columns

You specify what columns are retrieved by adding properties to your component.

// User.cfc
component extends="quick.models.BaseEntity" {

    property name="username";
    property name="email";

}

Now, only the id, username, and email columns will be retrieved.

Note: the primary key (id by default) will be retrieved regardless of the properties specified.

To prevent Quick from mapping a property to the database add the persistent="false" attribute to the property.

// User.cfc
// User.cfc
component extends="quick.models.BaseEntity" {

    property name="bcrypt" inject="@BCrypt" persistent="false";

    property name="username";
    property name="email";

}

If the column name in your table is not the column name you wish to use in quick, you can alias it using the column metadata attribute.

component extends="quick.models.BaseEntity" {

    property name="username" column="user_name";
    property name="countryId" column="FK_country_id";

}

Multiple datasource support

Quick uses a default datasource and default grammar, as described here. If you are using multiple datasources you can override default datasource by specifying a datasource metadata attribute on the component. If your extra datasource has a different grammar you can override your grammar as well by specifying a grammar attribute.

// User.cfc
component datasource="myOtherDatasource" grammar="PostgresGrammar" extends="quick.models.BaseEntity" {}

At the time of writing Valid grammar options are: MySQLGrammar,PostgresGrammar, MSSQLGrammar and OracleGrammar. Please check the qb docs for additional options.

Introduction

A CFML ORM Engine

Why?

Quick was built out of lessons learned and persistent challenges in developing complex RDBMS applications using built-in Hibernate ORM in CFML.

  • Hibernate ORM error messages often obfuscate the actual cause of the error because they are provided directly by the Java classes.

  • Complex CFML Hibernate ORM applications can consume significant memory and processing resources, making them cost-prohibitive and inefficient when used in microservices architecture.

  • Hibernate ORM is tied to the engine releases. This means that updates come infrequently and may be costly for non-OSS engine users.

  • Hibernate ORM is built in Java. This limits contributions from CFML developers who don't know Java or don't feel comfortable contributing to a Java project.

  • Hibernate ORM doesn't take advantage of a lot of dynamic- and meta-programming available in CFML. (Tools like CBORM have helped to bridge this gap.)

We can do better.

What?

Quick is an ORM (Object Relational Mapper) written in CFML for CFML. It provides an ActiveRecord implementation for working with your database. With it you can map database tables to components, create relationships between components, query and manipulate data, and persist all your changes to your database.

Prerequisites

You need the following configured before using Quick:

  • Configure a default datasource in your CFML engine

  • ColdBox 4.3+

  • Add a mapping for quick in your Application.cfc

  • Configure your BaseGrammar in config/ColdBox.cfc

See Getting Started for more details.

Supported Databases

Quick supports all databases supported by qb.

Example

Here's a "quick" example to whet your appetite.

We'll show the database structure using a migrations file. This isn't required to use quick, but it is highly recommended.

// 2017_11_10_122835_create_users_table.cfc
component {

    function up() {
        schema.create( "users", function( table ) {
            table.increments( "id" );
            table.string( "username" ).unique();
            table.string( "email" ).unique();
            table.string( "password" );
            table.timestamp( "createdDate" );
            table.timestamp( "updatedDate" );
        } );    
    }

}
// User
component extends="quick.models.BaseEntity" {

    // the name of the table is the pluralized version of the model
    // all fields in a table are mapped by default
    // both of these points can be configured on a per-entity basis

}
// handlers/Users.cfc
component {

    // /users/:id
    function show( event, rc, prc ) {
        // this finds the User with an id of 1 and retrieves it
        prc.user = getInstance( "User" ).findOrFail( rc.id );
        event.setView( "users/show" );
    }

}
<!-- views/users/show.cfm -->
<cfoutput>
    <h1>Hi, #prc.user.getUsername()#!</h1>
</cfoutput>

Now that you've seen an example, dig in to what you can do with Quick!

Prior Art, Acknowledgements, and Thanks

Quick is backed by qb. Without qb, there is no Quick.

Quick is inspired heavily by Eloquent in Laravel. Thank you Taylor Otwell and the Laravel community for a great library.

Development of Quick is sponsored by Ortus Solutions. Thank you Ortus Solutions for investing in the future of CFML.

Contributing

hasOne

Defining

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:

return hasOne( "UserProfile", "FK_userID" );

The inverse of hasOne is belongsTo. 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.

// UserProfile.cfc
component extends="quick.models.BaseEntity" {

    function user() {
        return belongsTo( "User" );
    }

}

Eager Loading

The Problem

Let's imagine a scenario where you are displaying a list of posts. You fetch the posts:

prc.posts = getInstance( "Post" ).limit( 25 ).get():

And start looping through them:

<cfoutput>
    <h1>Posts</h1>
    <ul>
        <cfloop array="#prc.posts.get()#" item="post">
            <li>#post.getTitle()# by #post.getAuthor().getUsername()#</li>
        </cfloop>
    </ul>
</cfoutput>

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.

The Solution

with

You can eager load a relationship with the with method call.

prc.posts = getInstance( "Post" )
    .with( "user" )
    .limit( 25 )
    .get();

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:

// Post.cfc
component extends="quick.models.BaseEntity" {

    function author() {
        return belongsTo( "User" );    
    }

}

To eager load the User in the snippet above you would call pass author to the with method.

getInstance( "Post" ).with( "author" );

For this operation, only two queries will be executed:

SELECT * FROM `posts` LIMIT 25

SELECT * FROM `users` WHERE `id` IN (1, 2, 3, 4, 5, 6, ...)

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 multiple relationships by passing an array of relation names to with or by calling with multiple times.

load

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 Collections.

Updating Existing Entities

save

Updates are handled identically to inserts when using the save method. The only difference is that instead of starting with a new entity, we start with an existing entity.

var user = getInstance( "User" ).find( 1 );
user.setPassword( "newpassword" );
user.save();

update

You can update multiple fields at once using the update method. This is similar to the create method for creating new entities.

var user = getInstance( "User" ).find( 1 );
user.update( {
   email = "[email protected]",
   password = "newpassword"
} );

There is no need to call save when using the update method.

updateAll

Updates can be performed against any number of entities that match a given query.

getInstance( "User" )
    .where( "lastLoggedIn", ">", dateAdd( "m", 3, now() ) )
    .updateAll( {
        "active" = 0
    } );

Deleting Entities

delete

You can delete an entity by calling the delete method on it.

var user = getInstance( "User" ).find( 1 )
user.delete();

Note: The entity will still exist in any variables you have stored it in, even though it has been deleted from the database.

deleteAll

Just like updateAll, you can delete many records from the database by specifying a query with constraints and then calling the deleteAll method.

getInstance( "User" )
    .whereActive( false )
    .deleteAll();

Additionally, you can pass in an array of ids to deleteAll to delete only those ids.

getInstance( "User" ).deleteAll( [ 4, 10, 22 ] );

Serialization

getMemento

The memento pattern is an established pattern in ColdBox apps. A memento in this case is a simple representation of your entity using arrays, structs, and simple values.

For instance, the following example shows a User entity and its corresponding memento:

You can modify the memento by overriding the getMemento function on your entity.

$renderData

The $renderData method is a special method for ColdBox. When returning a model from a handler, this method will be called and the value returned will be used as the serialized response. This let's you simply return an entity from a handler for your API.

QuickCollection also defines a $renderData method, which will delegate the call to each entity in the collection and return the array of serialized entities.

component extends="quick.models.BaseEntity" {

    property name="id";
    property name="username";
    property name="email";
    property name="password";
    property name="createdDate";
    property name="modifiedDate";

}
{
    "id" = 1,
    "username" = "JaneDoe",
    "email" = "[email protected]",
    "password" = "$2a$04$2nVI5rPOfl6.hrflkhBWOObO5Z7lXGJpi1vlosY74NrL/CKdpWqZS"
    "createdDate" = "{ts '2018-03-12 16:14:10'}",
    "modifiedDate" = "{ts '2018-03-12 16:14:10'}"
}
component extends="quick.models.BaseEntity" {

    property name="id";
    property name="username";
    property name="email";
    property name="password";
    property name="createdDate";
    property name="modifiedDate";

    function getMemento() {
        return {
            id = getId(),
            username = getUsername(),
            email = getEmail(),
            createdDate = dateFormat( getCreatedDate(), "MM/DD/YYYY" ),
            // can also use getAttribute if you want to bypass a custom getter
            modifiedDate = dateFormat( getAttribute( "modifiedDate" ), "MM/DD/YYYY" )
        };
    }

}
{
    "id" = 1,
    "username" = "JaneDoe",
    "email" = "[email protected]",
    "createdDate" = "03/12/2018",
    "modifiedDate" = "03/12/2018"
}
component {

    // /users/:id
    function show( event, rc, prc ) {
        return getInstance( "User" ).findOrFail( rc.id );
    }

}
{
    "id" = 1,
    "username" = "JaneDoe",
    "email" = "[email protected]",
    "createdDate" = "03/12/2018",
    "modifiedDate" = "03/12/2018"
}
component {

    function index( event, rc, prc ) {
        return getInstance( "User" ).all();    
    }

}
[
    {
        "id" = 1,
        "username" = "JaneDoe",
        "email" = "[email protected]",
        "createdDate" = "03/12/2018",
        "modifiedDate" = "03/12/2018"
    },
    {
        "id" = 2,
        "username" = "JohnDoe",
        "email" = "[email protected]",
        "createdDate" = "03/14/2018",
        "modifiedDate" = "03/15/2018"
    }
]

Query Scopes

Definition

Query scopes are a way to encapsulate query constraints in your entities while giving them readable names .

A Practical Example

For instance, let's say that you need to write a report for subscribers to your site. Maybe you track subscribers in a users table with a boolean flag in a subscribed column. Additionally, you want to see the oldest subscribers first. You keep track of when a user subscribed in a subscribedDate column. Your query might look as follows:

var subscribedUsers = getInstance( "User" )
    .where( "subscribed", 1 )
    .orderBy( "subscribedDate" )
    .get();

Now nothing is wrong with this query. It retrieves the data correctly and you continue on with your day.

Later, you need to retrieve a list of subscribed users for a different part of the site. So, you write a query like this:

var subscribedUsers = getInstance( "User" )
    .where( "subscribed", 1 )
    .get();

We've duplicated the logic for how to retrieve active users now. If the database representation changed, we'd have to change it in multiple places. For instance, what if instead of keeping track of a boolean flag in the database, we just checked that the subscribedDate column wasn't null?

var subscribedUsers = getInstance( "User" )
    .whereNotNull( "subscribedDate" )
    .get()

Now we see the problem. Let's look at the solution.

The key here is that we are trying to retrieve subscribed users. Let's add a scope to our User entity for subscribed:

component extends="quick.models.BaseEntity" {

    function scopeSubscribed( query ) {
        return query.where( "subscribed", 1 );
    }

}

Now, we can use this scope in our query:

var subscribedUsers = getInstance( "User" )
    .subscribed()
    .get();

We can use this on our first example as well, for our report.

var subscribedUsers = getInstance( "User" )
    .subscribed()
    .orderBy( "subscribedDate" )
    .get();

We've successfully encapsulated our concept of a subscribed user!

We can add as many scopes as we'd like. Let's add one for longestSubscribers.

component extends="quick.models.BaseEntity" {

    function scopeLongestSubscribers( query ) {
        return query.orderBy( "subscribedDate" );
    }

    function scopeSubscribed( query ) {
        return query.where( "subscribed", 1 );
    }

}

Now our query is as follows:

var subscribedUsers = getInstance( "User" )
    .subscribed()
    .longestSubscribers()
    .get();

Best of all, we can reuse those scopes anywhere we see fit without duplicating logic.

Usage

All query scopes are methods on an entity that begin with the scope keyword. You call these functions without the scope keyword (as shown above).

Each scope is passed two arguments: query, a reference to the current QueryBuilder instance; and args, any arguments passed to the scope call.

polymorphicHasMany

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" );
    }

}

hasManyThrough

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 .

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 intermediateKey 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 intermediateKey as the fourth argument.

Lastly, the owningKey is the primary key of the entity. Usually this is just id. You can override this by passing in the owningKey as the fifth 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.

// User.cfc
component extends="quick.models.BaseEntity" {

    function permissions() {
       return hasManyThrough( "Permission" );
    }

}
// Permission.cfc
component extends="quick.models.BaseEntity" {

    function users() {
       return hasManyThrough( "User" );
    }

}
// 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" );
return hasManyThrough( "Permission", "UserPermission", "FK_permissionID", "FK_userID", "userID" );

belongsToMany

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.

// User.cfc
component extends="quick.models.BaseEntity" {

    function permissions() {
       return belongsToMany( "Permission" );
    }

}
// Permission.cfc
component extends="quick.models.BaseEntity" {

    function users() {
       return belongsToMany( "User" );
    }

}

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.

permissions_users
- permissionId
- userId

As you can see, Quick uses a convention of combining the entity table names with an underscore (_) 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.

// User.cfc
component extends="quick.models.BaseEntity" {

    function permissions() {
       return belongsToMany( "Permission", "user_permission_map" );
    }

}

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:

return belongsToMany( "Permission", "user_permission_map", "FK_UserId", "FK_PermissionID" );

The inverse of belongsToMany is also belongsToMany. The foreignKey and relatedKey arguments are swapped on the inverse side of the relationship.

// Permission.cfc
component extends="quick.models.BaseEntity" {

    function user() {
        belongsToMany( "User", "user_permission_map", "FK_PermissionID", "FK_UserId" );
    }

}

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.

// User.cfc
component extends="quick.models.BaseEntity" {

    function userPermissions() {
        return hasMany( "UserPermission" );
    }

    function permissions() {
        return hasManyThrough( "Permission", "UserPermission" );
    }

}

attach

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.

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 );

detach

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.

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 );

sync

Sometimes you just want the related entities to be a list you give it. For these situations, use the sync method.

var post = getInstance( "Post" ).findOrFail( 1 );

post.tags().sync( [ 2, 3, 6 ] );

Now, no matter what relationships existed before, this Post will only have three tags associated with it.

Interception Points

Quick allows you to hook in to multiple points in the entity lifecycle.

quickPreLoad

Fired before attempting to load an entity from the database.

This method is only called for find actions.

interceptData structure

Key

Description

id

The id of the entity attempting to be loaded

metadata

The metadata of the entity

quickPostLoad

Fired after loading an entity from the database.

This method is only called for find actions.

interceptData structure

Key

Description

entity

The entity loaded

quickPreSave

Fired before saving an entity to the database.

This method is called for both insert and update actions.

interceptData structure

Key

Description

entity

The entity to be saved

quickPostSave

Fired after saving an entity to the database.

This method is called for both insert and update actions.

interceptData structure

Key

Description

entity

The entity that was saved

quickPreInsert

Fired before inserting an entity into the database.

interceptData structure

Key

Description

entity

The entity to be inserted

quickPostInsert

Fired after inserting an entity into the database.

interceptData structure

Key

Description

entity

The entity that was inserted

quickPreUpdate

Fired before updating an entity in the database.

interceptData structure

Key

Description

entity

The entity to be updated

quickPostUpdate

Fired after updating an entity in the database.

interceptData structure

Key

Description

entity

The entity that was updated

quickPreDelete

Fired before deleting a entity from the database.

interceptData structure

Key

Description

entity

The entity to be deleted

quickPostDelete

Fired after deleting a entity from the database.

interceptData structure

Key

Description

entity

The entity that was deleted

Relationship Types

Relationship Types

Creating New Entities

save

New Quick entities can be created and persisted to the database by creating a new entity instance, setting the attributes on the entity, and then calling the save method.

When we call save, the record is persisted from the database and the primary key is set to the auto-generated value (if any).

create

Another option is to use the create method. This method accepts a struct of data and creates a new instance with the data specified.

var user = getInstance( "User" );
user.setUsername( "JaneDoe" );
user.setEmail( "[email protected]" );
user.setPassword( "mypass1234" );
user.save();
var user = getInstance( "User" ).create( {
    "username" = "JaneDoe",
    "email" = "[email protected]",
    "password" = "mypass1234"
} );
hasOne
hasMany
belongsTo
belongsToMany
hasManyThrough
polymorphicBelongsTo
polymorphicHasMany

Custom Getters & Setters

Sometimes you want to use a different value in your code than is stored in your database. Perhaps you want to enforce that setting a password always is hashed with BCrypt. Maybe you have a Date value object that you want wrapping each of your dates. You can accomplish this using custom getters and setters.

A custom getter or setter is simply a function in your entity.

To retrieve the attribute value fetched from the database, call getAttribute passing in the name of the attribute.

To set an attribute for saving to the database, call setAttribute passing in the name and the value.

component extends="quick.models.BaseEntity" {

    property name="bcrypt" inject="@BCrypt";

    function setPassword( value ) {
        return setAttribute( "password", bcrypt.value );
    }

    function getCreatedDate( value ) {
        return dateFormat( getAttribute( "createdDate" ), "DD MMM YYYY" );
    } 

}

Note: Custom getters and setters with not be called when hydrating a model from the database.

Getting Started

Configure a default datasource in your CFML engine

You can do this any way you'd like: through the web admin, in Application.cfc, or using cfconfig.

Make sure to set this.datasource in your Application.cfc so Quick knows which datasource to use.

Add a mapping for quick in your Application.cfc

For a default installation in a ColdBox template, the following line will do the trick.

this.mappings[ "/quick" ] = COLDBOX_APP_ROOT_PATH & "/modules/quick";

Configure your defaultGrammar in config/ColdBox.cfc

BaseGrammar is a module setting for Quick. Set it in your config/ColdBox.cfc like so:

moduleSettings = {
    quick = {
        defaultGrammar = "MySQLGrammar"
    }
};

Valid options are any of the qb supported grammars.

If you want to use a different datasource and/or grammar for individual entitities you can do so by adding some metadata attributes to your entities.