Utah
Ortus Solutions
Prolific Module Author
1 wife, 3 kids, 1 dog
Type 1 Diabetic
15 Minutes
Give relevant names to important collections of SQL code. (scopes, relationships, etc.)
Make queries easy to compose at runtime to get the exact data you want in the most efficient way (subselects, eager loading, etc.)
Get out of your way when you need or want to write barebones SQL.
Map to a database table.
Expose columns as properties.
Expresses relationships between entities
Can define custom logic as needed
component extends="quick.models.BaseEntity" accessors="true" {
property name="bcrypt" inject="@BCrypt" persistent="false";
property name="id";
property name="email";
property name="password" column="hashed_password";
public User function setPassword( required string password ){
return assignAttribute(
"password",
bcrypt.hashPassword( arguments.password )
);
}
public struct function getMemento(){
return { "email" : variables.getEmail() };
}
}
Goal: Capture addition user information in the registration form.
1 hour
var users = getInstance( "User" ).all();
var posts = getInstance( "Post" )
.whereNotNull( "publishedDate" )
.get();
var posts = getInstance( "Post" )
.whereNotNull( "publishedDate" )
.paginate( rc.page, rc.maxrows );
Returns an array of entities
Goal: Show all existing Posts
Define how two entities are connected.
Extendible at runtime.
Can be eager or lazy loaded.
component extends="quick.models.BaseEntity" accessors="true" {
function author() {
return belongsTo( "User" );
// same as
// return belongsTo( "User", "userID", "ID" );
}
}
var user = getInstance( "User" ).findOrFail( 1 );
var post = getInstance( "Post" ).findOrFail( 1 );
post.author().associate( user ); // does not save automatically!
// same as
post.setAuthor( user );
post.save(); // now the database has been updated.
// Returns a single related entity
var userAgain = post.getAuthor();
user.isSameAs( userAgain ); // true
// Returns a relationship query
var qb = post.author();
// Manually set fields and manually call `save`
var user = getInstance( "User" );
user.setUsername( "JaneDoe" );
user.setEmail( "jane@example.com" );
user.setPassword( "mypass1234" );
user.save();
// Set fields using `fill` and manually call `save`
var user = getInstance( "User" );
user.fill( {
"username": "JaneDoe",
"email": "jane@example.com",
"password": "mypass1234"
} );
user.save();
// Set fields and save entity all at once
var user = getInstance( "User" ).create( {
"username": "JaneDoe",
"email": "jane@example.com",
"password": "mypass1234"
} );
Goal: Create a new Post from the UI
15 Minutes
var user = getInstance( "User" )
.where( "username", rc.username )
.first();
var user = getInstance( "User" )
.where( "username", rc.username )
.firstOrFail();
var user = getInstance( "User" )
.find( rc.userID );
var user = getInstance( "User" )
.findOrFail( rc.userID );
Returns a single entity
Goal: Display a single Post
// Manually set fields and manually call `save`
var user = getInstance( "User" ).findOrFail( 1 );
user.setPassword( "newpassword" );
user.save();
// Set fields using `fill` and manually call `save`
var user = getInstance( "User" ).findOrFail( 1 );
user.fill( {
"password": "newpassword"
} );
user.save();
// Set fields and save entity all at once
var user = getInstance( "User" ).findOrFail( 1 );
user.update( {
"password" = "newpassword"
} );
// Update all entities that match a query
getInstance( "User" )
.where( "lastLoggedIn", ">", dateAdd( "m", 3, now() ) )
.updateAll( {
"active" = 0
} );
// Deleting a single entity
var user = getInstance( "User" ).findOrFail( 1 );
user.delete();
// Deleting all entities that match a query
getInstance( "User" )
.whereActive( false )
.deleteAll();
Goal: Allow for editing, updating, and deleting of Posts
component extends="quick.models.BaseEntity" accessors="true" {
function posts() {
return hasMany( "Post" );
// same as
// return hasMany( "Post", "userID", "id" );
}
}
var user = getInstance( "User" ).findOrFail( 1 );
var posts = user.getPosts(); // returns an array
var newPost = getInstance( "Post" ).fill( { /* ... */ } );
user.posts().save( newPost ); // sets the correct foreign keys and saves
// same as
user.posts().create( { /* ... */ } );
// Returns a relationship query
// can be further constrained using Quick and qb methods
var qb = user.posts();
Goal: Add a Commenting System
var subscribedUsers = getInstance( "User" )
.where( "subscribed", true )
.get();
component extends="quick.models.BaseEntity" accessors="true" {
function scopeSubscribed( qb ) {
return qb.where( "subscribed", true );
}
}
var subscribedUsers = getInstance( "User" )
.subscribed()
.get();
var postsForUser = getInstance( "Post" )
.where( "userID", user.getID() )
.get();
var postsForUser = user.posts().get();
component extends="quick.models.BaseEntity" accessors="true" {
function posts() {
return hasMany( "Post" );
}
}
Goal: Make existing code cleaner, more readable, and more expressive using Scopes and Relationships
15 Minutes
Goal: Solve the N+1 problem with Eager Loading
component displayname="Post" extends="quick.models.BaseEntity" accessors="true" {
function tags() {
return belongsToMany( "Tag" );
// same as
return belongsToMany(
"Tag",
"posts_tags",
"postID",
"tagID",
"id", // posts.id
"id" // tags.id
);
}
}
component displayname="Tag" extends="quick.models.BaseEntity" accessors="true" {
function posts() {
return belongsToMany( "Post" );
}
}
// attach
var post = getInstance( "Post" ).findOrFail( 1 );
var tag = getInstance( "Tag" ).create( { "name": "miscellaneous" });
post.tags().attach( tag.getId() ); // pass an id (or array of ids)
post.tags().attach( tag ); // or pass an entity (or array of entities)
// detach
var post = getInstance( "Post" ).findOrFail( 1 );
var tag = getInstance("Tag").firstWhere( "name", "miscellaneous" );
post.tags().detach( tag.getId() ); // pass an id (or array of ids)
post.tags().detach( tag ); // or pass an entity (or array of entities)
// sync
var post = getInstance( "Post" ).findOrFail( 1 );
post.tags().sync( [ 2, 3, 6 ] ); // or an array of entities
Goal: Allow adding tags to posts
1 hour
component displayname="User" extends="quick.models.BaseEntity" accessors="true" {
property name="id";
property name="username";
property name="email";
property name="password";
property name="createdDate";
property name="modifiedDate";
}
(Usage in an APIs)
var memento = user.getMemento();
/*
{
"id" = 1,
"username" = "JaneDoe",
"email" = "jane@example.com",
"password" = "$2a$04$2nVI5rPOfl6.hrflkhBWOObO5Z7lXGJpi1vlosY74NrL/CKdpWqZS"
"createdDate" = "{ts '2018-03-12 16:14:10'}",
"modifiedDate" = "{ts '2018-03-12 16:14:10'}"
}
*/
component displayname="User" extends="quick.models.BaseEntity" accessors="true" {
property name="id";
property name="username";
property name="email";
property name="password";
property name="createdDate";
property name="modifiedDate";
}
var memento = user.getMemento(
excludes = [ "password" ]
);
/*
{
"id" = 1,
"username" = "JaneDoe",
"email" = "jane@example.com",
"createdDate" = "{ts '2018-03-12 16:14:10'}",
"modifiedDate" = "{ts '2018-03-12 16:14:10'}"
}
*/
var users = getInstance( "User" )
.asMemento( excludes = [ "password" ] )
.get()
/*
[
{
"id" = 1,
"username" = "JaneDoe",
"email" = "jane@example.com",
"createdDate" = "{ts '2018-03-12 16:14:10'}",
"modifiedDate" = "{ts '2018-03-12 16:14:10'}"
},
{
"id" = 2,
"username" = "JohnDoe",
"email" = "john@example.com",
"createdDate" = "{ts '2018-03-12 16:14:10'}",
"modifiedDate" = "{ts '2018-03-12 16:14:10'}"
},
...
]
*/
Goal: Allow async liking of posts
var post = getInstance( "Post" )
.addSubselect( "authorEmail", function( qb ) {
return qb.select( "email" )
.from( "users" )
.whereColumn( "users.id", "posts.userID" );
} )
.firstOrFail();
post.getAuthorEmail(); // john@example.com
component displayname="Post" extends="quick.models.BaseEntity" accessors="true" {
property name="id";
property name="title";
property name="body";
property name="createdDate";
property name="modifiedDate";
function author() {
return belongsTo( "User" );
}
}
var post = getInstance( "Post" )
.addSubselect( "authorEmail", "author.email" )
.firstOrFail();
post.getAuthorEmail(); // john@example.com
component displayname="User" extends="quick.models.BaseEntity" accessors="true" {
property name="id";
property name="email";
property name="createdDate";
property name="modifiedDate";
}
var post = getInstance( "Post" )
.addSubselect( "commentsCount", function( qb ) {
qb.selectRaw( "COUNT(*)" )
.from( "comments" )
.whereColumn( "comments.postID", "posts.id" );
} )
.firstOrFail();
post.getCommentsCount(); // 5
component displayname="Post" extends="quick.models.BaseEntity" accessors="true" {
property name="id";
property name="title";
property name="body";
property name="createdDate";
property name="modifiedDate";
function comments() {
return hasMany( "Comment" );
}
}
var post = getInstance( "Post" )
.withCount( [ "comments" ] )
.firstOrFail();
post.getCommentsCount(); // 5
Goal: Reduce queries with subselects and relationship counts
var sql = getInstance( "Post" )
.latest()
.toSQL();
/*
SELECT *
FROM `posts`
ORDER BY `posts`.createdDate` DESC
*/
var posts = getInstance( "Post" )
.dump( label = "before latest scope" )
.latest()
.dump( label = "after latest scope" )
.get();
// config/ColdBox.cfc
component {
function configure() {
logbox = {
debug = [ "qb.models.Grammars" ]
};
}
}
Goal: Utilize TDD to create an Author Profile page
15 Minutes
Goal: Add the ability to schedule posts to publish in the future using TDD