BoxLang 🚀 A New JVM Dynamic Language Learn More...
Quick was built out of lessons learned and persistent challenges in developing complex RDBMS applications using built-in Hibernate ORM in CFML.
We can do better.
Quick is an ORM (Object Relational Mapper) written in CFML for CFML. It provides an ActiveRecord implementation for working with your database. With it you can map database tables to components, create relationships between components, query and manipulate data, and persist all your changes to your database.
You need the following configured before using Quick:
quick in your Application.cfc
BaseGrammar in config/ColdBox.cfc
See Getting Started for more details.
Quick supports all databases supported by qb.
Here's a "quick" example to whet your appetite.
We'll show the database structure using a migrations
file. This isn't required to use quick, but it is
highly recommended.
// 2017_11_10_122835_create_users_table.cfc
component {
function up() {
schema.create( "users", function( table ) {
table.increments( "id" );
table.string( "username" ).unique();
table.string( "email" ).unique();
table.string( "password" );
table.timestamp( "createdDate" );
table.timestamp( "updatedDate" );
} );
}
}
// User
component extends="quick.models.BaseEntity" {
// the name of the table is the pluralized version of the model
// this can be configured on a per-entity basis
}
// handlers/Users.cfc
component {
// /users/:id
function show( event, rc, prc ) {
// this finds the User with an id of 1 and retrieves it
prc.user = getInstance( "User" ).findOrFail( rc.id );
event.setView( "users/show" );
}
}
<!-- views/users/show.cfm -->
<cfoutput>
<h1>Hi, #prc.user.getUsername()#!</h1>
</cfoutput>
Now that you've seen an example, dig in to what you can do with Quick!
Quick passes query options through to queryExecute, so
applications can use the query cache provided by their CFML engine.
This works with collection queries and primary-key lookups:
var users = getInstance( "User" ).get(
options = { cachedWithin : createTimeSpan( 0, 0, 5, 0 ) }
);
var user = getInstance( "User" ).find(
rc.id,
{ cachedWithin : createTimeSpan( 0, 0, 5, 0 ) }
);
An entity can also configure defaults for every query by assigning
_queryOptions in its pseudo-constructor:
component extends="quick.models.BaseEntity" {
variables._queryOptions = {
cachedWithin : createTimeSpan( 0, 0, 5, 0 )
};
}
Query caching stores database results, not live Quick entities or loaded relationships. Cache lifetime and invalidation are managed by the CFML engine, so use short lifetimes for data that Quick or another process may update. For application-specific invalidation or distributed caching, cache entity mementos in CacheBox at the service layer and rehydrate them through Quick's public APIs.
asQuery() changes how Quick returns results; it still
returns a QuickBuilder. When a plain qb query needs a subquery built
with Quick, pass the underlying qb builder using getQB()
or retrieveQuery():
var userIds = getInstance( "User" ).where( "active", true ).select( "id" );
var posts = getInstance( "QueryBuilder@qb" )
.from( "posts" )
.whereIn( "user_id", userIds.getQB() )
.get();
Keep Quick-specific scopes and column selection on the Quick builder
before crossing this boundary. asQuery() is not a
replacement for retrieveQuery().
belongsToMany().create( attributes, pivotAttributes )
saves the related entity, attaches its pivot row, and returns the
saved entity. Supply additional pivot values in the second argument.
Configure a pivot model with using() when reading pivot
attributes through a model with casts or behavior.
A through relationship can traverse multiple intermediate entities and relationship types. Creating a target does not identify which intermediate records to reuse or create. Persist the target and intermediate associations explicitly through the direct relationship methods, using a transaction when those writes must succeed together. Quick does not infer and cascade-create an arbitrary through path.
Parallel eager loading is disabled by default. To allow independent eager-load branches to run concurrently on Lucee and BoxLang, explicitly enable it in your ColdBox module settings:
moduleSettings = {
quick = {
parallelEagerLoading = true
}
};
Then opt in on individual queries:
var posts = getInstance( "Post" )
.with( [ "author", "tags" ], true )
.get();
With parallelEagerLoading = false (the default), Quick
does not create or validate a parallel executor, and all eager loading
remains sequential even when .with( relations, true ) is
used. Enabling the module setting still requires the per-query opt-in
shown above. Adobe ColdFusion, a single top-level relationship, and
active database transactions use the sequential path. Parallel workers
retrieve and hydrate separate branches; matching the results onto the
parent entities happens on the calling thread. Worker errors and
timeouts propagate to the caller.
Quick's module settings include
parallelEagerLoadingMaxThreads (default 4),
parallelEagerLoadingTimeout (default 60000
milliseconds per batch), and parallelEagerLoadingExecutor
(the name of an optional application-provided bounded ColdBox
executor). When parallel eager loading is enabled, Quick creates and
manages a fixed executor when none is supplied. Account for database
connection capacity when increasing concurrency.
Quick includes Laravel-inspired model factories under
quick.resources.testing. Define application factories
outside of your production model code:
// tests/resources/factories/UserFactory.cfc
component extends="quick.resources.testing.Factory" {
struct function definition() {
return {
username : "factory-#lCase( createUUID() )#",
firstName : "Factory",
lastName : "User"
};
}
any function administrator() {
return state( { type : "admin" } );
}
}
Create a manager in your test base class and expose a short
factory() helper:
variables.factoryManager = new quick.resources.testing.FactoryManager(
wirebox = getWireBox(),
factoryPath = "tests.resources.factories"
);
any function factory( required string name ) {
return variables.factoryManager.factory( arguments.name );
}
Factories support default definitions, explicit and named states,
counts, sequences, attribute closures, and afterMaking
and afterCreating callbacks. make() returns
unsaved Quick entities, while create() persists through
the entity's normal save() lifecycle:
var admin = factory( "User" ).administrator().create();
var users = factory( "User" ).count( 3 ).create();
var unsavedUser = factory( "User" ).make( { firstName : "Override" } );
Factories do not manage database transactions. Integration tests
should start a transaction around each test and roll it back in
finally, ensuring both passing and failing tests leave
the database unchanged.
All factory implementation classes are isolated beneath
resources/testing; production deployment tooling may
exclude that directory. Quick does not load or register these classes
during normal module startup.
To run the tests, first clone this repo and run a box install.
Quick's test suite runs specifically on MySQL, so you will need a MySQL database to run the tests. If you do not have one, Docker provides an easy way to start one.
docker run -d --name quick -p 3306:3306 -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=quick -e MYSQL_USER=quick -e MYSQL_PASSWORD=quick mysql:5
Finally, copy the .env.example file to .env
and fill in the values for your database.
Quick is backed by qb. Without qb, there is no Quick.
Quick is inspired heavily by Eloquent in Laravel. Thank you Taylor Otwell and the Laravel community for a great library.
Development of Quick is sponsored by Ortus Solutions. Thank you Ortus Solutions for investing in the future of CFML.
Join us in our Ortus Community and become a valuable member of this project Quick ORM. We are looking forward to hearing from you!
join inside QuickBuilder
(bf5d726)joiningQuery name in qb 13.0.18
(1a95350)booleanFormat actually returns a string
(c823213)isQuickBuilder checks on BoxLang (70b1710)options for retrieval methods
(0e0fc11)withCount and withSum (1262c8d)box namespace for CommandBox compatibility
(a05076f)inferSqlType
(54ca2aa)fill method on the relationship object
(a2da133)newAttributes and originalAttributes to the preUpdate event
(4dd8496)newEntity function and variable name.
(81aedbb)hasManyThrough function now returns HasManyDeep relationships
(1af491a)hasManyDeep and hasManyDeepBuilder relationships. (5331b36)hasManyThrough relationships
(3f3de36)IRelationship interface
(026d7e0)wirebox:targetId to support CommandBox (164247d)withCount and withSum (e1b17cf)withCount method to easily add relationship counts to entities (8524ef8)with (5491ba7)when closures (96a8f3a)
$
box install quick