All pages
Powered by GitBook
1 of 9

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Defining An Entity

To get started with Quick, you need an entity. You start by extending quick.models.BaseEntity.

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

You can generate Quick entities from CommandBox! Install quick-commands and use quick entity create to get started!

Tables

We don't need to tell Quick what table name to use for our entity. By default, Quick uses the pluralized, snake_cased name of the component for the table name. That means for our User entity Quick will assume the table name is users. For an entity with multiple words like PasswordResetToken the default table would be password_reset_tokens. You can override this by specifying a table metadata attribute on the component.

Inheritance

For more information on using inheritance and child tables in your relational database model, see .

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.

Quick also assumes a key type that is auto-incrementing. If you would like a different key type, override thekeyType function and return the desired key type from that function.

Quick ships with the following key types:

  • AutoIncrementingKeyType

  • NullKeyType

  • ReturningKeyType

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

Attributes

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

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

Make sure to include the primary key (id by default) as a property.

Persistent

To prevent Quick from mapping a property to a database column add the persistent="false" attribute to the property. This is needed mostly when using dependency injection.

Column

If the column name in your table is not the column name you wish to use in Quick, you can specify the column name using the column metadata attribute. The attribute will be available using the name of the attribute.

Null Values

To work around CFML's lack of null, you can use the nullValue and convertToNull attributes.

nullValue defines the value that is considered null for a attribute. By default it is an empty string. ("")

convertToNull is a flag that, when false, will not try to insert null in to the database. By default this flag is true.

Read Only

The readOnly attribute will prevent setters, updates, and inserts to a attribute when set to true.

SQL Type

In some cases you will need to specify an exact SQL type for your attribute. Any value set for the sqltype attribute will be used when inserting or updating the attribute in the database. It will also be used when you use the attribute in a where constraint.

Casts

The casts attribute allows you to use a value in your CFML code as a certain type while being a different type in the database. A common example of this is a boolean which is usually represented as a BIT in the database.

Two casters ship with Quick: BooleanCast@quick and JsonCast@quick. You can add them using those mappings to any applicable columns.

Custom Casts

The casts attribute must point to a WireBox mapping that resolves to a component that implements the quick.models.Casts.CastsAttribute interface. (The implements keyword is optional.) This component defines how to get a value from the database in to the casted value and how to set a casted value back to the database. Below is an example of the built-in BooleanCast, which comes bundled with Quick.

Casted values are lazily loaded and cached for the lifecycle of the component. Only cast values that have been loaded will have set called on them when persisting to the database.

Casts can be composed of multiple fields as well. Take this Address value object, for example:

This component is not a Quick entity. Instead it represents a combination of fields stored on our User entity:

Noticed that the casted address is neither persistent nor does it have a getter or setter created for it.

The last piece of the puzzle is our AddressCast component that handles casting the value to and from the native database values:

You can see that returning a struct of values from the set function assigns multiple attributes from a single cast.

Insert & Update

You can prevent inserting and updating a property by setting the insert or update attribute to false.

Formula, Computed, or Subselect properties

Quick handles formula, computed, or subselect properties using query scopes and the addSubselect helper method.

Multiple datasource support

Quick uses a default datasource and default grammar, as described . 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.

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

Comparing Entities

You can compare entities using the isSameAs and isNotSameAs methods. Each method takes another entity and returns true if the two objects represent the same entity.

Subclass Entities

By default, Quick supports basic component-level inheritance of entities, meaning that a child component inherits the properties ( and ability to overload ) its parent. A common, object-oriented relational database pattern, however is to provide additional definition on parent tables ( and classes ) within child tables which contain a foreign key.

Quick supports two types of child classes: Discriminated and Subclassed child entities. In both cases, loading any child class will also deliver the data of its parent class.

Subclass Entities

Let's say, for example, that I have a Media entity, which is used to catalog and organize all media items loaded in to my application.

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

UUIDKeyType

  • RowIDKeyType

  • Subclass Entities
    Check out the docs in query scopes to learn more.
    here
    qb docs
    // User.cfc
    component table="t_users" extends="quick.models.BaseEntity" accessors="true" {}
    // User.cfc
    component extends="quick.models.BaseEntity" accessors="true" {
    
        variables._key = "user_id";
    
    }
    // User.cfc
    component extends="quick.models.BaseEntity" accessors="true" {
    
        function keyType() {
            return variables._wirebox.getInstance( "UUIDKeyType@quick" );
        }
    
    }
    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 );
    
    }
    // User.cfc
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="id";
        property name="username";
        property name="email";
    
    }
    // User.cfc
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="bcrypt" inject="@BCrypt" persistent="false";
    
        property name="id";
        property name="username";
        property name="email";
    
    }
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="id";
        property name="username" column="user_name";
        property name="countryId" column="FK_country_id";
    
    }
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="id";
        property name="number" convertToNull="false";
        property name="title" nullValue="REALLY_NULL";
    
    }
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="id";
        property name="createdDate" readonly="true";
    
    }
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="id";
        property name="number" sqltype="cf_sql_varchar";
    
    }
    // User.cfc
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="id";
        property name="active" casts="BooleanCast@quick";
    
    }
    // BooleanCast.cfc
    component implements="CastsAttribute" {
    
        /**
         * Casts the given value from the database to the target cast type.
         *
         * @entity      The entity with the attribute being casted.
         * @key         The attribute alias name.
         * @value       The value of the attribute.
         *
         * @return      The casted attribute.
         */
        public any function get(
            required any entity,
            required string key,
            any value
        ) {
            return isNull( arguments.value ) ? false : booleanFormat( arguments.value );
        }
    
        /**
         * Returns the value to assign to the key before saving to the database.
         *
         * @entity      The entity with the attribute being casted.
         * @key         The attribute alias name.
         * @value       The value of the attribute.
         *
         * @return      The value to save to the database. A struct of values
         *              can be returned if the cast value affects multiple attributes.
         */
        public any function set(
            required any entity,
            required string key,
            any value
        ) {
            return arguments.value ? 1 : 0;
        }
    
    }
    // Address.cfc
    component accessors="true" {
    
        property name="streetOne";
        property name="streetTwo";
        property name="city";
        property name="state";
        property name="zip";
    
        function fullStreet() {
            var street = [ getStreetOne(), getStreetTwo() ];
            return street.filter( function( part ) {
                return !isNull( part ) && part != "";
            } ).toList( chr( 10 ) );
        }
    
        function formatted() {
            return fullStreet() & chr( 10 ) & "#getCity()#, #getState()# #getZip()#";
        }
    
    }
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="id";
        property name="username";
        property name="firstName" column="first_name";
        property name="lastName" column="last_name";
        property name="password";
    
        property name="address"
            casts="AddressCast"
            persistent="false"
            getter="false"
            setter="false";
        property name="streetOne";
        property name="streetTwo";
        property name="city";
        property name="state";
        property name="zip";
    
    }
    component implements="quick.models.Casts.CastsAttribute" {
    
        property name="wirebox" inject="wirebox";
    
        /**
         * Casts the given value from the database to the target cast type.
         *
         * @entity      The entity with the attribute being casted.
         * @key         The attribute alias name.
         * @value       The value of the attribute.
         * @attributes  The struct of attributes for the entity.
         *
         * @return      The casted attribute.
         */
        public any function get(
            required any entity,
            required string key,
            any value
        ) {
            return wirebox.getInstance( dsl = "Address" )
                .setStreetOne( entity.retrieveAttribute( "streetOne" ) )
                .setStreetTwo( entity.retrieveAttribute( "streetTwo" ) )
                .setCity( entity.retrieveAttribute( "city" ) )
                .setState( entity.retrieveAttribute( "state" ) )
                .setZip( entity.retrieveAttribute( "zip" ) );
        }
    
        /**
         * Returns the value to assign to the key before saving to the database.
         *
         * @entity      The entity with the attribute being casted.
         * @key         The attribute alias name.
         * @value       The value of the attribute.
         * @attributes  The struct of attributes for the entity.
         *
         * @return      The value to save to the database. A struct of values
         *              can be returned if the cast value affects multiple attributes.
         */
        public any function set(
            required any entity,
            required string key,
            any value
        ) {
            return {
                "streetOne": arguments.value.getStreetOne(),
                "streetTwo": arguments.value.getStreetTwo(),
                "city": arguments.value.getCity(),
                "state": arguments.value.getState(),
                "zip": arguments.value.getZip()
            };
        }
    
    }
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="id";
        property name="email" column="email" update="false" insert="true";
    
    }
    // User.cfc
    component
        datasource="myOtherDatasource"
        grammar="PostgresGrammar@qb"
        extends="quick.models.BaseEntity"
        accessors="true"
    {
        // ....
    }
    var userOne = getInstance( "User" ).findOrFail( 1 );
    var userTwo = getInstance( "User" ).findOrFail( 1 );
    
    userOne.isSameAs( userTwo ); // true
    userOne.isNotSameAs( userTwo ); // false
    My Media entity contains all of the properties which are common to every media item uploaded in to my application. Let's say, however, that I need to have specific attributes that are available on only media for my Book entity ( e.g. whether the image is the cover photo, for example ). I can create a child class of BookMedia which extends my Media entity. When loaded, all of the properties of Media will be loaded along with the custom attributes which apply to my BookMedia object:

    Note the additional component attribute joincolumn. The presence of this attribute on a child class signifies that it is a child entity of the parent and that the parent's properties should be loaded whenever the BookMedia entity is loaded. In addition, the primary key of the entity is that of the parent.

    Note that a table attribute is required on a child entity if the parent entity has one. This is because ColdBox will perform a deep merge on the entire inheritance chain for metadata properties. If a parent class has a table attribute, it will show up as the child's table attribute.

    Child entities can be retrieved by queries specific to their own properties:

    Or properties on the parent class can be used as first-class properties within the query:

    Child entities can be retrieved, individually, using the value of the joinColumn, which should be a foreign key to the parent identifier column:

    Now my Book entity can use its extended media class to retrieve media items which are specific to its own purpose:

    Discriminated Entities

    A discriminated child class functions, basically, in the same way as a subclassed entity, with one exception: The parent entity is aware of the discriminated child due to a discriminatorValue attribute and will return a specific subclass when a retrieval is performed through the parent Entity. This pattern is also known as polymorphic association.

    Quick supports two different types of discriminated entities defined by single-table inheritance (STI) or multi-table inheritance (MTI). Your database schema will determine the most appropriate inheritance pattern for your use case.

    Let's take our BookMedia class again, but this time, define it as a discriminated entity.

    The first step is to add the discriminatorColumn attribute to the Media entity, which is used to differentiate the subclass entities. Next, define an array of possible discriminated entities for the parent. This is so we don't have to scan all Quick components just to determine if there are any discriminated entities.

    Then we set a discriminatorValue property on the child class, the value of which is stored in the parent entity table, which differentiates the different subclasses.

    Then we set a discriminatorValue property on the child class, the value of which is stored in the parent entity table:

    We aren’t entirely done yet. Finally, we must tell Quick whether we are using multi-table inheritance or single-table inheritance so it can map the database data to the subclass entities.

    Multi-Table Inheritance (MTI)

    If the data for your discriminated entities is normalized across multiple tables, you should use the multi-table inheritance (MTI) method for creating discriminated entities. In the example below, The Media entity has two subclasses, MediaBook and MediaAlbum. The discriminator column for the entities is the type column in the media table and the distinct properties for each subclass come from the media_book and media_album tables.

    To inform Quick that our database schema follows the MTI pattern, we must add a joinColumn and table values for each subclass.

    Single Table Inheritance (STI)

    If the data for your discriminated entities lives in a single table, you should use the single-table inheritance (STI) method for creating discriminated entities. In the example below, the Media entity has two subclasses, MediaBook and MediaAlbum. The discriminator column for the entities is the type column in the media table, and the distinct properties for each subclass come from a single table.

    To inform Quick that our database schema follows the STI pattern, we must also add singleTableInheritance=true to the parent entity.

    Retrieving Discriminated Entities

    Once the parent and child entities are defined, new BookMedia entities will be saved with a type value of "book" in the media table. As such, the following query will result in only entities of BookMedia being returned:

    If our Media table contains a combination of non-book and book media, then the collection returned when querying all records will contain a mix of Media entities such as BookMedia and AlbumMedia

    If you want to create a brand-new entity of a specific subclass, you can do so by calling newChildEntity( discriminatorValue ) like this:

    Summary

    Discriminated and child class entities, allow for a more Object oriented approach to entity-specific relationships by allowing you to eliminate pivot/join tables and extend the attributes of the base class.

    Getting Started

    qb is the engine that powers Quick. It is highly recommended that you become familiar with the

    Configure a default datasource in your CFML engine

    You can do this any way you'd like: through the web admin, in

    component 
        table="media" 
        extends="quick.models.BaseEntity" 
        accessors="true"
    {
    
        property name="id";
        property name="uploadFileName";
        property name="fileLocation";
        property name="fileSizeBytes";
    
    }
    component
        extends="Media"
        table="book_media"
        joinColumn="FK_media"
        accessors="true"
    {
        property name="displayOrder";
        property name="designation";
    
        function approvalStatus(){
            return belongsTo( "Book", "FK_book" );
        }
    
    }
    var coverPhotos = getInstance( "BookMedia" )
                        .where( "designation", "cover" )
                        .orderBy( "displayOrder", "ASC" );
    var smallCoverPhotos = getInstance( "BookMedia" )
                        .where( "designation", "cover" )
                        .where( "fileSizeBytes", "<", 40000 )
                        .orderBy( "displayOrder", "ASC" )
                        .orderBy( "uploadFileName", "ASC" );
    var myBookMediaItem = getInstance( "BookMedia" ).get( myId );
    function media(){
        return hasMany( "BookMedia", "FK_book" ).orderBy( "displayOrder", "ASC" );
    }
    // Media (parent entity)
    component 
        extends="quick.models.BaseEntity" 
        accessors="true"
        table="media" 
        discriminatorColumn="type" // the database column that determines the subclass
    {
    
        property name="id";
        property name="uploadFileName";
        property name="fileLocation";
        property name="fileSizeBytes";
        
        // Array of all possible subclass entities
        variables._discriminators = [
            "BookMedia"
        ];
    
    }
    // BookMedia (subclass)
    component
        extends="Media"
        accessors="true"
        discriminatorValue="book" // column value unique to this subclass
    {
        property name="displayOrder";
        property name="designation";
    
    }
    // BookMedia (subclass entity using MTI)
    component 
        extends="Media"
        table="media_book" // table for BookMedia data
        joinColumn="mediaId" // column to join on
        accessors="true"
    {
    // Media (parent entity)
    component 
        extends="quick.models.BaseEntity" 
        accessors="true"
        table="media" 
        discriminatorColumn="type" 
        singleTableInheritance="true" // Enable STI
    {
    var bookMedia = getInstance( "Media" ).where( "type", "book" ).get();
    var allMedia = getInstance( "Media" ).all();
    var newBookMedia = getInstance( "Media" ).newChildEntity( "BookMedia" );
    Application.cfc
    , or using
    .

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

    Quick can use multiple datasources, but it makes it easier to use when you don't have to deal with that.

    Download Quick

    The easiest way to download Quick is to use ForgeBox with CommandBox. Just run the following from the root of your application:

    box install quick

    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

    Quick will auto discover your grammar by default on startup. To avoid this check, set a BaseGrammar.

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

    Valid options are any of the qb supported grammars. At the time of writing valid grammar options are: MySQLGrammar@qb, PostgresGrammar@qb, SqlServerGrammar@qb and OracleGrammar@qb. You can also have qb discover your grammar on application init using AutoDiscover@qb. Please check the qb docs for additional options.

    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.

    qb documentation.
    cfconfig
    moduleSettings = {
        quick = {
            defaultGrammar = "MySQLGrammar@qb"
        }
    };

    Working with Entities

    isLoaded

    Checks if the entity was loaded from the database.

    Name

    Type

    Required

    Default

    Description

    A loaded entity has a tie to the database. It has either been loaded from the database or saved to the database. An unloaded entity is one created in code but not saved to the database yet.

    Deleting Entities

    delete

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

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

    No parameters

    var user = getInstance( "User" );
    user.isLoaded(); // false
    user.save();
    user.isLoaded(); // true
    deleteAll

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

    Name

    Type

    Required

    Default

    Description

    ids

    array

    false

    []

    An optional array of ids to add to the previously configured query. The ids will be added to a WHERE IN statement on the primary key columns.

    Deletes matching entities according to the configured query.

    Additionally, you can pass in an array of ids to deleteAll to delete only those ids. Note that any previously configured constraints will still apply.

    var user = getInstance( "User" ).find( 1 );
    user.delete();
    getInstance( "User" )
        .whereActive( false )
        .deleteAll();
    getInstance( "User" ).deleteAll( [ 4, 10, 22 ] );

    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.

    update

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

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

    By default, if you have a key in the struct that doesn't match a property in the entity the update method will fail. If you add the optional argument ignoreNonExistentAttributes set to true, those missing keys are ignored. Now you can pass the rc scope from your submitted form directly into the update method and not worry about any other keys in the rc like event that would cause the method to fail.

    updateOrCreate

    Updates an existing record or creates a new record with the given attributes.

    updateAll

    Updates matching entities with the given attributes according to the configured query. This is analagous to

    fresh

    Retrieves a new entity from the database with the same key value as the current entity. Useful for seeing any changes made to the record in the database. This function executes a query.

    refresh

    Refreshes the attributes data for the entity with data from the database. This differs from fresh in that it operates on the current entity instead of returning a new one. This function executes a query.

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

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of key / value pairs to update on the entity.

    ignoreNonExistentAttributes

    boolean

    false

    false

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of attributes to restrict the query. If no entity is found the attributes are filled on the new entity created.

    newAttributes

    struct

    false

    {}

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    The attributes to update on the matching records.

    force

    boolean

    false

    false

    Name

    Type

    Required

    Default

    Description

    No arguments

    ``

    Name

    Type

    Required

    Default

    Description

    No arguments

    ``

    qb's update method.

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    A struct of attributes to update on the found entity or the new entity if no entity is found.

    If true, skips read-only entity and read-only attribute checks.

    var user = getInstance( "User" ).find( 1 );
    user.update( {
       email = "[email protected]",
       password = "newpassword"
    } );
    var user = getInstance( "User" ).find( 1 );
    user.update( rc, true );
    var user = getInstance( "User" ).updateOrCreate( {
        "username": "newuser"
    } );
    getInstance( "User" )
        .where( "lastLoggedIn", ">", dateAdd( "m", 3, now() ) )
        .updateAll( {
            "active" = 0
        } );
    var user = getInstance( "User" ).findOrFail( rc.userID );
    var sameUser = user.fresh();
    var user = getInstance( "User" ).findOrFail( rc.userID );
    user.refresh(); // user now has updated data from the database

    Query Scopes and Subselects

    What are Scopes?

    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:

    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:

    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?

    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:

    Now, we can use this scope in our query:

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

    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.

    Now our query is as follows:

    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 the query, a reference to the current QuickBuilder instance, as the first argument. Any other arguments passed to the scope will be passed in order after that.

    Scopes that Return Values

    All of the examples so far either returned the QuickBuilder object or nothing. Doing so lets you continue to chain methods on your Quick entity. If you instead return a value, Quick will pass on that value to your code. This lets you use scopes as shortcut methods that work on a query.

    For example, maybe you have a domain method to reset passwords for a group of users, and you want the count of users updated returned.

    Global Scopes

    Occasionally, you want to apply a scope to each retrieval of an entity. An example of this is an Admin entity which is just a User entity with a type of admin. Global Scopes can be registered in the applyGlobalScopes method on an entity. Inside this entity you can call any number of scopes:

    These scopes will be applied to the query without needing to call the scope again.

    If you have a global scope applied to an entity that you need to temporarily disable, you can disable them individually using the withoutGlobalScope method:

    Subselects

    Subselects are a useful way to grab data from related tables without having to execute the full relationship. Sometimes you just want a small piece of information like the lastLoginDate of a user, not the entire Login relationship. Subselects are perfect for this use case. You can even use subselects to provide the correct key for dynamic subselect relationships. We'll show how both work here.

    Quick handles subselect properties (or computed or formula properties) through query scopes. This allows you to dynamically include a subselect. If you would like to always include a subselect, add it to your entity's

    Here's an example of grabbing the lastLoginDate for a User:

    We'd add this subselect by calling our scope:

    We can even constrain our User entity based on the value of the subselect, so long as we've called the scope adding the subselect first (or made it a global scope).

    Or add a new scope to User based on the subselect:

    In this example, we are using the addSubselect helper method. Here is that function signature:

    You might be wondering why not use the logins relationship? Or even logins().latest().limit( 1 ).get()? Because that executes a second query. Using a subselect we get all the information we need in one query, no matter how many entities we are pulling back.

    Using Relationships in Subselects

    In most cases the values you want as subselects are values from your entity's relationships. In these cases, you can use a shortcut to define your subselect in terms of your entity's relationships represented as a dot-delimited string.

    Let's re-write the above subselect for lastLoginDate for a User using the existing relationship:

    Much simpler! In addition to be much simpler this code is also more dynamic and reusable. We have a relationship defined for logins if we need to fetch them. If we change how the logins relationship is structured, we only have one place we need to change.

    With the query cleaned up using existing relationships, you might find yourself adding subselects directly in your handlers instead of behind scopes. This is fine in most cases. Keep an eye on how many places you use the subselect in case you need to re-evaluate and move it behind a scope.

    Dynamic Subselect Relationships

    Subselects can be used in conjunction with relationships to provide a dynamic, constrained relationship. In this example we will pull the latest post for a user.

    This can be executed as follows:

    As you can see, we are loading the id of the latest post in a subquery and then using that value to eager load the latestPost relationship. This sequence will only execute two queries, no matter how many records are loaded.

    Virtual Attributes

    Virtual attributes are attributes that are not present on the table backing the Quick entity. A Subselect is an example of a virtual attribute. Other examples could include calculated counts or CASE statement results.

    By default, if you add a virtual column to a Quick query, you won't see anything in the entity. This is because Quick needs to have an attribute defined to map the result to. You can create a virtual attribute in these cases.

    This step is unnecessary when using the addSubselect helper method.

    Here's an example including the result of a CASE statement as a field:

    With this code, we could now access the publishedStatus just like any other attribute. It will not be updated, inserted, or saved though, as it is just a virtual column.

    The appendVirtualAttribute method adds the given name as an attribute available in the entity.

    appendVirtualAttribute

    Creates a virtual attribute for the given name.

    It is likely that Quick will introduce more helper methods in the future making these calls simpler.

    Argument

    Type

    Required

    Default

    Description

    name

    string

    true

    The name for the subselect. This will be available as an attribute.

    subselect

    string OR QueryBuilder OR Function

    true

    Argument

    Type

    Required

    Default

    Description

    name

    string

    true

    The attribute name to create.

    list of global scopes.

    Either a dot-delimited string representing a relationship chain ending with an attribute name, a QueryBuilder object, or a closure that configures a QueryBuilder object can be provided. If a closure is provided it will be passed a query object as its only parameter. The resulting query object will be used to computed the subselect.

    var subscribedUsers = getInstance( "User" )
        .where( "subscribed", true )
        .orderBy( "subscribedDate" )
        .get();
    var subscribedUsers = getInstance( "User" )
        .where( "subscribed", true )
        .get();
    var subscribedUsers = getInstance( "User" )
        .whereNotNull( "subscribedDate" )
        .get();
    component extends="quick.models.BaseEntity" accessors="true" {
    
        function scopeSubscribed( query ) {
            return query.where( "subscribed", true );
        }
    
    }
    var subscribedUsers = getInstance( "User" )
        .subscribed()
        .get();
    var subscribedUsers = getInstance( "User" )
        .subscribed()
        .orderBy( "subscribedDate" )
        .get();
    component extends="quick.models.BaseEntity" accessors="true" {
    
        function scopeLongestSubscribers( query ) {
            return query.orderBy( "subscribedDate" );
        }
    
        function scopeSubscribed( query ) {
            return query.where( "subscribed", true );
        }
    
    }
    var subscribedUsers = getInstance( "User" )
        .subscribed()
        .longestSubscribers()
        .get();
    component extends="quick.models.BaseEntity" accessors="true" {
    
        function scopeOfType( query, type ) {
            return query.where( "type", type );
        }
    
    }
    var subscribedUsers = getInstance( "User" )
        .ofType( "admin" )
        .get();
    component extends="quick.models.BaseEntity" accessors="true" {
    
        property name="id";
        property name="username";
        property name="password";
        property name="type";
    
        function scopeOfType( query, type = "limited" ) {
            return query.where( "type", type );
        }
    
        function scopeResetPasswords( query ) {
            return query.updateAll( { "password" = "" } ).result.recordcount;
        }
    
    }
    getInstance( "User" ).ofType( "admin" ).resetPasswords(); // 1
    component extends="User" table="users" accessors="true" {
    
        function applyGlobalScopes( qb ) {
            qb.ofType( "admin" );
        }
    
    }
    var admins = getInstance( "Admin" ).all();
    // SELECT * FROM users WHERE type = 'admin'
    var admins = getInstance( "Admin" ).withoutGlobalScope( [ "ofType" ] ).all();
    // SELECT * FROM users
    component extends="quick.models.BaseEntity" accessors="true" {
    
        /* properties */
    
        function logins() {
            return hasMany( "Login" ).latest();
        }
    
        function scopeAddLastLoginDate( qb ) {
            qb.addSubselect( "lastLoginDate", newEntity( "Login" )
                .select( "timestamp" )
                .whereColumn( "users.id", "user_id" )
            );
        }
    
    }
    var user = getInstance( "User" ).addLastLoginDate().first();
    user.getLastLoginDate(); // {ts 2019-05-02 08:24:51}
     var user = getInstance( "User" )
         .addLastLoginDate()
         .where( "lastLoginDate", ">", "2019-05-10" )
         .all();
    function scopeLoggedInAfter( qb, required date afterDate ) {
        return qb.where( "lastLoginDate", ">", afterDate );
    }
    component extends="quick.models.BaseEntity" accessors="true" {
    
        /* properties */
    
        function logins() {
            return hasMany( "Login" );
        }
    
        function scopeAddLastLoginDate( qb ) {
            qb.addSubselect( "lastLoginDate", "logins.timestamp" );
        }
    
    }
    var user = getInstance( "User" )
        .addSubselect( "lastLoginDate", "logins.timestamp" )
        .first();
    user.getLastLoginDate(); // {ts 2019-05-02 08:24:51}
    component extends="quick.models.BaseEntity" accessors="true" {
    
        /* properties */
    
        function scopeWithLatestPost( qb ) {
            return qb.addSubselect( "latestPostId", newEntity( "Post" )
                .select( "id" )
                .whereColumn( "user_id", "users.id" )
                .orderBy( "created_date", "desc" )
            ).with( "latestPost" );
        }
    
        function latestPost() {
            return belongsTo( "Post", "latestPostId" );
        }
    
    }
    var users = getInstance( "User" ).withLatestPost().all();
    for ( var user in users ) {
        user.getLatestPost().getTitle(); // My awesome post, etc.
    }
    // Post.cfc
    component extends="quick.models.BaseEntity" accessors="true" {
    
        function scopeAddType( qb ) {
            qb.selectRaw( "
                    CASE
                        WHEN publishedDate IS NULL THEN 'unpublished'
                        ELSE 'published'
                    END AS publishedStatus
                " );
            appendVirtualAttribute( "publishedStatus" );
        }
    
    }

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

    We can shortcut the setters above using a fill method.

    fill

    Finds the first matching record or creates a new entity.

    Sets attributes data from a struct of key / value pairs. This method does the following, in order:

    1. Guard against read-only attributes.

    2. Attempt to call a relationship setter.

    3. Calls custom attribute setters for attributes that exist.

    4. Throws an error if an attribute does not exist (if ignoreNonExistentAttributes

    populate

    Populate is simply an alias for fill. Use whichever one suits you best.

    create

    Creates a new entity with the given attributes and then saves the entity.

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

    firstOrNew

    Finds the first matching record or returns an unloaded new entity.

    firstOrCreate

    Finds the first matching record or creates a new entity.

    findOrNew

    Returns the entity with the id value as the primary key. If no record is found, it returns a new unloaded entity.

    findOrCreate

    Returns the entity with the id value as the primary key. If no record is found, it returns a newly created entity.

    updateOrCreate

    Updates an existing record or creates a new record with the given attributes.

    Hydration Methods

    Hydration is a term to describe filling an entity with a struct of data and then marking it as loaded, without doing any database queries. For example, this might be useful when hydrating a user from session data instead of doing a query every request.

    hydrate

    Hyrdates an entity from a struct of data. Hydrating an entity fills the entity and then marks it as loaded.

    If the entity's keys are not included in the struct of data, a MissingHydrationKey is thrown.

    hydrateAll

    Hydrates a new collection of entities from an array of structs.

    var user = getInstance( "User" );
    user.setUsername( "JaneDoe" );
    user.setEmail( "[email protected]" );
    user.setPassword( "mypass1234" );
    user.save();

    include

    list or array

    false

    []

    List or array of keys to include when filling. All others will be ignored.

    exclude

    list or array

    false

    []

    List or array of keys to exclude when filling. All others will be filled.

    is
    false
    which is the default).

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of key / value pairs to fill in to the new entity.

    ignoreNonExistentAttributes

    boolean

    false

    false

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of key / value pairs to fill in to the new entity.

    ignoreNonExistentAttributes

    boolean

    false

    false

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of key / value pairs to fill in to the new entity.

    ignoreNonExistentAttributes

    boolean

    false

    false

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of attributes to restrict the query. If no entity is found the attributes are filled on the new entity returned.

    newAttributes

    struct

    false

    {}

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of attributes to restrict the query. If no entity is found the attributes are filled on the new entity created.

    newAttributes

    struct

    false

    {}

    Name

    Type

    Required

    Default

    Description

    id

    any

    true

    The id value to find.

    attributes

    struct

    false

    {}

    Name

    Type

    Required

    Default

    Description

    id

    any

    true

    The id value to find.

    attributes

    struct

    false

    {}

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of attributes to restrict the query. If no entity is found the attributes are filled on the new entity created.

    newAttributes

    struct

    false

    {}

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of key / value pairs to fill in to the entity.

    ignoreNonExistentAttributes

    boolean

    false

    false

    Name

    Type

    Required

    Default

    Description

    mementos

    array

    false

    []

    An array of structs to hydrate into entities.

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    A struct of attributes to fill on the new entity if no entity is found. These attributes are combined with attributes.

    A struct of attributes to fill on the created entity if no entity is found. These attributes are combined with attributes.

    A struct of attributes to fill on the new entity if no entity is found.

    A struct of attributes to use when creating the new entity if no entity is found.

    A struct of attributes to update on the found entity or the new entity if no entity is found.

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    var user = getInstance( "User" );
    user.fill( {
        "username": "JaneDoe",
        "email": "[email protected]",
        "password": "mypass1234"
    } );
    user.save();
    var user = getInstance( "User" ).create( {
        "username": "JaneDoe",
        "email": "[email protected]",
        "password": "mypass1234"
    } );
    var user = getInstance( "User" )
        .firstOrNew( { "username": rc.username } );
    var user = getInstance( "User" )
        .firstOrCreate( { "username": rc.username } );
    var user = getInstance( "User" ).findOrNew(
        9999,
          {
                  "firstName" : "doesnt",
                  "lastName"  : "exist"
          }
    );
    var user = getInstance( "User" ).findOrCreate(
        9999,
          {
            "username"  : "doesntexist",
                  "firstName" : "doesnt",
                  "lastName"  : "exist",
                  "password"  : "secret"
          }
    );
    var user = getInstance( "User" ).updateOrCreate( {
        "username": "newuser"
    } );
    var user = getInstance( "User" ).hydrate( {
        "id": 4,
        "username": "JaneDoe",
        "email": "[email protected]",
        "password": "mypass1234"
    } );
    
    user.isLoaded(); // true
    var users = getInstance( "User" ).hydrateAll( [
        {
            "id": 3,
            "username": "JohnDoe",
            "email": "[email protected]",
            "password": "mypass4321"
        },
        {
            "id": 4,
            "username": "JaneDoe",
            "email": "[email protected]",
            "password": "mypass1234"
        }
    ] );

    Retrieving Entities

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

    You can configure your query to retrieve entities using any qb method. It is highly recommended you become familiar with the qb documentation.

    Active Record

    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 arrays. For example:

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

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

    Quick Service

    A second way to retrieve results is to use a Quick Service. It is similar to a VirtualEntityService from cborm.

    The easiest way to create a Quick Service is to inject it using the quickService: dsl:

    If you have a existing Service, and you would like to extend the quickService, you can extend the quikc.models.BaseService and then call super.init inside of the service init function passing the name of the entity (for example your User Entity) shown below:

    Any method you can call on an entity can be called on the service. A new entity will be used for all calls to a Quick Service.

    If you need a new unsaved entity from your service, you can call myService.newEntity()

    Aggregates

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

    existsOrFail

    Returns true if any entities exist with the configured query. If no entities exist, it throws an EntityNotFound exception.

    Retrieval Methods

    all

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

    get

    Executes the configured query, eager loads any relations, and returns the entities in a new collection.

    paginate

    Executes the configured query, eager loads any relations, and returns the entities in the configured qb pagination struct.

    simplePaginate

    Executes the configured query, eager loads any relations, and returns the entities in the configured qb simple pagination struct.

    first

    Executes the configured query and returns the first entity found. If no entity is found, returns null.

    firstWhere

    Adds a basic where clause to the query and returns the first result.

    firstOrFail

    Executes the configured query and returns the first entity found. If no entity is found, then an EntityNotFound exception is thrown with the given or default error message.

    firstOrNew

    Finds the first matching record or returns an unloaded new entity.

    firstOrCreate

    Finds the first matching record or creates a new entity.

    find

    Returns the entity with the id value as the primary key. If no record is found, it returns null instead.

    findOrFail

    Returns the entity with the id value as the primary key. If no record is found, it throws an EntityNotFound exception.

    findOrNew

    Returns the entity with the id value as the primary key. If no record is found, it returns a new unloaded entity.

    findOrCreate

    Returns the entity with the id value as the primary key. If no record is found, it returns a newly created entity.

    newEntity

    You will probably not use newEntity very often if you use wirebox getinstance("someQuickEntity") to retrieve an entity, because that will already give you a new entity. If you are using a QuickService you will need this method for creating a new entity

    Hydration Methods

    Hydration is a term to describe filling an entity with a struct of data and then marking it as loaded, without doing any database queries. For example, this might be useful when hydrating a user from session data instead of doing a query every request.

    hydrate

    Hyrdates an entity from a struct of data. Hydrating an entity fills the entity and then marks it as loaded.

    If the entity's keys are not included in the struct of data, a MissingHydrationKey is thrown.

    hydrateAll

    Hydrates a new collection of entities from an array of structs.

    Custom Collections

    If you would like collections of entities to be returned as something besides an array, you can override the newCollection method. It receives the array of entities. You can return any custom collection you desire.

    newCollection

    Returns a new collection of the given entities. It is expected to override this method in your entity if you need to specify a different collection to return. You can also call this method with no arguments to get an empty collection.

    value

    any

    false

    The value with which to constrain the column. An expression (builder.raw()) can be passed as well.

    combinator

    string

    false

    "and"

    The boolean combinator for the clause (e.g. "and" or "or"). Default: "and"

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    ignoreNonExistentAttributes

    boolean

    false

    false

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    Name

    Type

    Required

    Default

    Description

    id

    any

    false

    An optional id to check if it exists.

    errorMessage

    any

    false

    "No [#entityName()#] found with id [#arguments.id#]"

    Name

    Type

    Required

    Default

    Description

    No arguments

    ``

    Name

    Type

    Required

    Default

    Description

    No arguments

    ``

    Name

    Type

    Required

    Default

    Description

    page

    numeric

    false

    1

    The page of results to return.

    maxRows

    numeric

    false

    25

    Name

    Type

    Required

    Default

    Description

    page

    numeric

    false

    1

    The page of results to return.

    maxRows

    numeric

    false

    25

    Name

    Type

    Required

    Default

    Description

    No arguments

    ``

    Name

    Type

    Required

    Default

    Description

    column

    any

    true

    The name of the column with which to constrain the query. A closure can be passed to begin a nested where statement.

    operator

    any

    true

    Name

    Type

    Required

    Default

    Description

    errorMessage

    any

    false

    "No [#entityName()#] found with constraints [#serializeJSON( retrieveQuery().getBindings() )#]"

    An optional string error message or callback to produce a string error message. If a callback is used, it is passed the unloaded entity as the only argument.

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of attributes to restrict the query. If no entity is found the attributes are filled on the new entity returned.

    newAttributes

    struct

    false

    {}

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of attributes to restrict the query. If no entity is found the attributes are filled on the new entity created.

    newAttributes

    struct

    false

    {}

    Name

    Type

    Required

    Default

    Description

    id

    any

    true

    The id value to find.

    Name

    Type

    Required

    Default

    Description

    id

    any

    true

    The id value to find.

    errorMessage

    any

    false

    "No [#entityName()#] found with id [#arguments.id#]"

    Name

    Type

    Required

    Default

    Description

    id

    any

    true

    The id value to find.

    attributes

    struct

    false

    {}

    Name

    Type

    Required

    Default

    Description

    id

    any

    true

    The id value to find.

    attributes

    struct

    false

    {}

    Name

    type

    required

    default

    description

    name

    string

    wirebox name of the quick entity

    Name

    Type

    Required

    Default

    Description

    attributes

    struct

    false

    {}

    A struct of key / value pairs to fill in to the entity.

    ignoreNonExistentAttributes

    boolean

    false

    false

    Name

    Type

    Required

    Default

    Description

    mementos

    array

    false

    []

    An array of structs to hydrate into entities.

    ignoreNonExistentAttributes

    boolean

    false

    false

    Name

    Type

    Required

    Default

    Description

    entities

    array

    false

    []

    The array of entities returned by the query.

    qb documentation

    An optional string error message or callback to produce a string error message. If a callback is used, it is passed the unloaded entity as the only argument.

    The number of rows to return.

    The number of rows to return.

    The operator to use for the constraint (i.e. "=", "<", ">=", etc.). A value can be passed as the operator and the value left null as a shortcut for equals (e.g. where( "column", 1 ) == where( "column", "=", 1 ) ).

    A struct of attributes to fill on the new entity if no entity is found. These attributes are combined with attributes.

    A struct of attributes to fill on the created entity if no entity is found. These attributes are combined with attributes.

    An optional string error message or callback to produce a string error message. If a callback is used, it is passed the unloaded entity as the only argument.

    A struct of attributes to fill on the new entity if no entity is found.

    A struct of attributes to use when creating the new entity if no entity is found.

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute.

    var users = getInstance( "User" ).all();
    
    for ( var user in users ) {
        writeOutput( user.getUsername() );
    }
    var users = getInstance( "User" ).all();
    
    prc.users.each( function( user ) {
        writeOutput( user.getUsername() );
    });
    var users = getInstance( "User" )
        .where( "active", 1 )
        .orderByDesc( "username" )
        .limit( 10 )
        .get();
    component {
    
        property name="userService" inject="quickService:User"
    
    }
    component singleton extends="quick.models.BaseService" {
    
        function init(){
            super.init( "User" );
        }
    
    }
    var users = userService
        .where( "active", 1 )
        .orderByDesc( "username" )
        .limit( 10 )
        .get();
    var doesUserExist = getInstance( "User" )
        .existsOrFail( rc.userID );
    var users = getInstance( "User" ).all();
    var posts = getInstance( "Post" )
        .whereNotNull( "publishedDate" )
        .get();
    var posts = getInstance( "Post" )
        .whereNotNull( "publishedDate" )
        .paginate( rc.page, rc.maxrows );
    // default response example
    {
        "results": [ User#1, User#2, ... ],
        "pagination": {
            "totalPages": 2,
            "maxRows": 25,
            "offset": 0,
            "page": 1,
            "totalRecords": 40
        }
    }
    var posts = getInstance( "Post" )
        .whereNotNull( "publishedDate" )
        .simplePaginate( rc.page, rc.maxrows );
    // default response example
    {
        "results": [ User#1, User#2, ... ],
        "pagination": {
            "hasMore": true,
            "maxRows": 25,
            "offset": 0,
            "page": 1
        }
    }
    var user = getInstance( "User" )
        .where( "username", rc.username )
        .first();
    var user = getInstance( "User" )
        .firstWhere( "username", rc.username );
    var user = getInstance( "User" )
        .where( "username", rc.username )
        .firstOrFail();
    var user = getInstance( "User" )
        .firstOrNew( { "username": rc.username } );
    var user = getInstance( "User" )
        .firstOrCreate( { "username": rc.username } );
    var user = getInstance( "User" )
        .find( rc.userID );
    var user = getInstance( "User" )
        .findOrFail( rc.userID );
    var user = getInstance( "User" ).findOrNew(
        9999,
          {
                  "firstName" : "doesnt",
                  "lastName"  : "exist"
          }
    );
    var user = getInstance( "User" ).findOrCreate(
        9999,
          {
            "username"  : "doesntexist",
                  "firstName" : "doesnt",
                  "lastName"  : "exist",
                  "password"  : "secret"
          }
    );
    var user = getInstance( "User" )
    
    //or
    
    property name="UserService" inject="quickService:User"; //inject a quick service
    
    var user = UserService.newEntity();
    var user = getInstance( "User" ).hydrate( {
        "id": 4,
        "username": "JaneDoe",
        "email": "[email protected]",
        "password": "mypass1234"
    } );
    
    user.isLoaded(); // true
    var users = getInstance( "User" ).hydrateAll( [
        {
            "id": 3,
            "username": "JohnDoe",
            "email": "[email protected]",
            "password": "mypass4321"
        },
        {
            "id": 4,
            "username": "JaneDoe",
            "email": "[email protected]",
            "password": "mypass1234"
        }
    ] );
    // Post.cfc
    component extends="quick.models.BaseEntity" accessors="true" {
    
        function newCollection( array entities = [] ) {
            return variables._wirebox.getInstance(
                name = "extras.QuickCollection",
                initArguments = {
                    "collection" = arguments.entities
                }
            );
        }
    
    }