Database & ORM
The entity hierarchy, the BaseService pattern, migrations, and seed data.
Database & ORM
Entity hierarchy
Every entity extends BaseEntity (@mappedsuperclass, itself extending cborm.models.ActiveEntity), which adds createdDate, modifiedDate, and an isActive soft-delete flag to every table automatically:
classDiagram
class BaseEntity {
+createdDate
+modifiedDate
+isActive
+getId()
+isLoaded()
+appendToMemento()
}
class User
class Role
class Permission
class APIToken
class RememberToken
class Passkey
class Setting
BaseEntity <|-- User
BaseEntity <|-- Role
BaseEntity <|-- Permission
BaseEntity <|-- APIToken
BaseEntity <|-- RememberToken
BaseEntity <|-- Passkey
BaseEntity <|-- Setting
User "many" --> "many" Role : roles
User "many" --> "many" Permission : à la carte
User "1" --> "many" APIToken
User "1" --> "many" RememberToken
User "1" --> "many" Passkey
Role "many" --> "many" Permission : role_permissions
dbcreate: "none" (set in public/Application.bx's ormSettings) means schema is owned exclusively by migrations — the ORM never auto-generates or alters tables.
Service layer pattern
Every service extends BaseService (@singleton, extends cborm.models.VirtualEntityService), which injects qb, coldbox, wirebox, and cachebox:template, and provides ensureSortOrder():
component
extends="BaseService"
singleton
threadSafe
{
property name="qb" inject="provider:QueryBuilder@qb";
property name="cache" inject="cachebox:template";
function list( struct criteria = {} ){
return newCriteria()
.when( criteria.search, function( c, term ){
c.like( "name", "%#term#%" );
} )
.list();
}
}
/**
* A role: a named bundle of permissions.
*/
class extends="app.models.BaseEntity" table="roles" {
property name="roleId" fieldtype="id" generator="uuid2" ormtype="string";
property name="name" type="string";
property name="permissions"
fieldtype="many-to-many"
cfc="Permission"
linktable="role_permissions";
}
component extends="app.models.BaseService" singleton threadSafe {
function getAllForLookup(){
return newCriteria().resultTransformer( "distinct" ).list();
}
}
Migrations
Powered by cfmigrations via the commandbox-migrations CLI module, configured in .cbmigrations.json (migrationsDirectory: resources/database/migrations/, seedsDirectory: resources/database/seeds/, connection built from the same DB_* env vars as public/Application.bx).
box migrate up # Run pending migrations
box migrate down # Rollback the last batch
box migrate reset # Rollback everything, then re-migrate
box migrate seed # Run database seeders
Migrations run in filename/timestamp order:
| Migration | Creates |
|---|---|
..._settings.bx | settings (GUID PK, unique name, longtext value) |
..._security.bx | permissions, roles, role_permissions (composite-PK join table, cascading FKs) |
..._users.bx | users (GUID PK, unique email, nullable password, JSON preferences), plus user_roles, user_permissions, user_remember_tokens, user_api_tokens, user_action_tokens, user_passkeys — every child table FK'd to users.userId with ON DELETE CASCADE |
..._auditlogs.bx | audit_logs append-only activity records with severity/category/action, actor and request metadata, and query indexes |
Seed data
resources/database/seeds/AdminData.bx, run via box migrate seed, creates:
- An Administrator role
- 16 permissions across four resources (
users,roles,permissions,settings), each withread/write/delete/admin— all assigned to the Administrator role - One admin user,
admin@cbgenesis.com, assigned the Administrator role
See Security & Permissions for how those slugs are enforced at the handler level.