Database & ORM

The entity hierarchy, the BaseService pattern, migrations, and seed data.

On this page

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:

MigrationCreates
..._settings.bxsettings (GUID PK, unique name, longtext value)
..._security.bxpermissions, roles, role_permissions (composite-PK join table, cascading FKs)
..._users.bxusers (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.bxaudit_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 with read/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.

Edit this page Download Markdown Last updated Sep 1, 2026, 7:55:53 PM