Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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.
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 qb documentation.
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:
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.
Calling qb's aggregate methods (count
, max
, etc.) will return the appropriate value instead of an entity or collection of entities.
Returns true if any entities exist with the configured query. If no entities exist, it throws an EntityNotFound exception.
Retrieves all the records for an entity. Calling all
will ignore any non-global constraints on the query.
Executes the configured query, eager loads any relations, and returns the entities in a new collection.
Executes the configured query, eager loads any relations, and returns the entities in the configured qb pagination struct.
Executes the configured query and returns the first entity found. If no entity is found, returns null
.
Adds a basic where clause to the query and returns the first result.
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.
Finds the first matching record or returns an unloaded new entity.
Finds the first matching record or creates a new entity.
Returns the entity with the id value as the primary key. If no record is found, it returns null instead.
Returns the entity with the id value as the primary key. If no record is found, it throws an EntityNotFound
exception.
Returns the entity with the id value as the primary key. If no record is found, it returns a new unloaded entity.
Returns the entity with the id value as the primary key. If no record is found, it returns a newly created entity.
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.
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.
Hydrates a new collection of entities from an array of structs.
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.
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.
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.
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:
Guard against read-only attributes.
Attempt to call a relationship setter.
Calls custom attribute setters for attributes that exist.
Throws an error if an attribute does not exist (if ignoreNonExistentAttributes
is false
which is the default).
Populate is simply an alias for fill
. Use whichever one suits you best.
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.
Finds the first matching record or returns an unloaded new entity.
Finds the first matching record or creates a new entity.
Returns the entity with the id value as the primary key. If no record is found, it returns a new unloaded entity.
Returns the entity with the id value as the primary key. If no record is found, it returns a newly created entity.
Updates an existing record or creates a new record with the given attributes.
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.
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.
Hydrates a new collection of entities from an array of structs.
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.
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.
Updates an existing record or creates a new record with the given attributes.
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.
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.
Updates matching entities with the given attributes according to the configured query. This is analagous to
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#]"
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
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
The number of rows to return.
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
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 )
).
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"
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
{}
A struct of attributes to fill on the new entity if no entity is found. These attributes are combined with attributes
.
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 attributes to restrict the query. If no entity is found the attributes are filled on the new entity created.
newAttributes
struct
false
{}
A struct of attributes to fill on the created entity if no entity is found. These attributes are combined with attributes
.
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
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#]"
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
id
any
true
The id value to find.
attributes
struct
false
{}
A struct of attributes to fill on the new entity if no entity is found.
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
true
The id value to find.
attributes
struct
false
{}
A struct of attributes to use when creating the new entity if no entity is found.
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 entity.
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
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.
Name
Type
Required
Default
Description
entities
array
false
[]
The array of entities returned by the query.
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
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
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
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 attributes to restrict the query. If no entity is found the attributes are filled on the new entity returned.
newAttributes
struct
false
{}
A struct of attributes to fill on the new entity if no entity is found. These attributes are combined with attributes
.
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 attributes to restrict the query. If no entity is found the attributes are filled on the new entity created.
newAttributes
struct
false
{}
A struct of attributes to fill on the created entity if no entity is found. These attributes are combined with attributes
.
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
true
The id value to find.
attributes
struct
false
{}
A struct of attributes to fill on the new entity if no entity is found.
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
true
The id value to find.
attributes
struct
false
{}
A struct of attributes to use when creating the new entity if no entity is found.
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 attributes to restrict the query. If no entity is found the attributes are filled on the new entity created.
newAttributes
struct
false
{}
A struct of attributes to update on the found entity or the new entity if no entity is found.
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 entity.
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
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.
Name | Type | Required | Default | Description |
attributes | struct |
|
| A struct of attributes to restrict the query. If no entity is found the attributes are filled on the new entity created. |
newAttributes | struct |
|
| A struct of attributes to update on the found entity or the new entity if no entity is found. |
ignoreNonExistentAttributes | boolean |
|
| 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 |
|
| The attributes to update on the matching records. |
force | boolean |
|
| If true, skips read-only entity and read-only attribute checks. |
Name | Type | Required | Default | Description |
No arguments | `` |
Name | Type | Required | Default | Description |
No arguments | `` |
Name | Type | Required | Default | Description |
attributes | struct |
|
| A struct of key / value pairs to update on the entity. |
ignoreNonExistentAttributes | boolean |
|
| If true, does not throw an exception if an attribute does not exist. Instead, it skips the non-existent attribute. |
qb is the engine that powers Quick. It is highly recommended that you become familiar with the qb documentation.
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.
Quick can use multiple datasources, but it makes it easier to use when you don't have to deal with that.
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
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";
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
, MSSQLGrammar@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.
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!
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.
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
UUIDKeyType
keyType
can be any component that adheres to the keyType
interface, so feel free to create your own and distribute them via ForgeBox.
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.
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.
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.
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
.
The readOnly
attribute will prevent setters, updates, and inserts to a attribute when set to true
.
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.
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.
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.
You can prevent inserting and updating a property by setting the insert
or update
attribute to false
.
Quick handles formula, computed, or subselect properties using query scopes and the addSubselect
helper method. Check out the docs in query scopes to learn more.
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.
At the time of writing Valid grammar options are: MySQLGrammar@qb
, PostgresGrammar@qb
, SqlServerGrammar@qb
and OracleGrammar@qb
. Please check the qb docs for additional options.
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.
Query scopes are a way to encapsulate query constraints in your entities while giving them readable names .
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.
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.
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.
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 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 list of global scopes.
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.
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.
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 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.
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.
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.
Just like updateAll
, you can delete many records from the database by specifying a query with constraints and then calling the deleteAll
method.
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.
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.
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
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.
Argument
Type
Required
Default
Description
name
string
true
The attribute name to create.