Basic Crud - ActiveEntity
Let's do a basic example of how to work with cborm when doing basic CRUD (Create-Read-Update-Delete). We will generate a ColdBox App, connect it to a database and leverage ActiveEntity for a nice quick CRUD App.
The source code for this full example can be found in Github: https://github.com/coldbox-samples/cborm-crud-demo or in ForgeBox: https://forgebox.io/view/cborm-crud-demo

ColdBox App

Let's start by creating a ColdBox app and preparing it for usage with ORM:
1
# Create folder
2
mkdir myapp --cd
3
# Scaffold App
4
coldbox create app
5
# Install cborm, dotenv, and cfconfig so we can get the CFML engine talking to the DB fast.
6
install cborm,commandbox-dotenv,commandbox-cfconfig
7
# Update the .env file
8
cp .env.example .env
Copied!

Setup Environment

Season the environment file (.env) with your database credentials and make sure that database exists:
1
# ColdBox Environment
2
APPNAME=ColdBox
3
ENVIRONMENT=development
4
5
# Database Information
6
DB_CONNECTIONSTRING=jdbc:mysql://127.0.0.1:3306/cborm?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useLegacyDatetimeCode=true
7
DB_CLASS=com.mysql.jdbc.Driver
8
DB_DRIVER=MySQL
9
DB_HOST=127.0.0.1
10
DB_PORT=3306
11
DB_DATABASE=cborm
12
DB_USER=root
13
DB_PASSWORD=cborm
14
15
# S3 Information
16
S3_ACCESS_KEY=
17
S3_SECRET_KEY=
18
S3_REGION=us-east-1
19
S3_DOMAIN=amazonaws.com
Copied!

Setup ORM

Now open the Application.cfc and let's configure the ORM by adding the following in the pseudo constructor and adding two lines of code to the request start so when we reinit the APP we can also reinit the ORM.
Application.cfc
1
// Locate the cborm module for events
2
this.mappings[ "/cborm" ] = COLDBOX_APP_ROOT_PATH & "modules/cborm";
3
4
// The default dsn name in the ColdBox scaffold
5
this.datasource = "coldbox";
6
// ORM Settings + Datasource
7
this.ormEnabled = "true";
8
this.ormSettings = {
9
cfclocation = [ "models" ], // Where our entities exist
10
logSQL = true, // Remove after development to false.
11
dbcreate = "update", // Generate our DB
12
automanageSession = false, // Let cborm manage it
13
flushAtRequestEnd = false, // Never do this! Let cborm manage it
14
eventhandling = true, // Enable events
15
eventHandler = "cborm.models.EventHandler", // Who handles the events
16
skipcfcWithError = true // Yes, because we must work in all CFML engines
17
};
18
19
// request start
20
public boolean function onRequestStart( string targetPage ){
21
// If we reinit our app, reinit the ORM too
22
if( application.cbBootstrap.isFWReinit() )
23
ormReload();
24
25
// Process ColdBox Request
26
application.cbBootstrap.onRequestStart( arguments.targetPage );
27
28
return true;
29
}
Copied!
To change the datasource name to something you like then update it here and in the .cfconfig.json file. Once done, issue a server restart and enjoy your new datasource name.

Start Server

Let's start a server and start enjoying the fruits of our labor:
1
# Start a default Lucee Server
2
server start
Copied!
If you get a Could not instantiate connection provider: org.lucee.extension.orm.hibernate.jdbc.ConnectionProviderImpl error on startup here. It means that you hit the stupid Lucee bug where on first server start the ORM is not fully deployed. Just issue a server restart to resolve this.

Create Entity - Person.cfc

Let's start by creating a Person object with a few properties, let's use CommandBox for this and our super duper coldbox create orm-entity command:
1
coldbox create orm-entity
2
entityName="Person"
3
activeEntity=true
4
properties=name,age:integer,lastVisit:timestamp
Copied!
This will generate the models/Person.cfc as an ActiveEntity object and even create the unit test for it.
Person.cfc
1
/**
2
* A cool Person entity
3
*/
4
component persistent="true" table="Person" extends="cborm.models.ActiveEntity"{
5
6
// Primary Key
7
property name="id" fieldtype="id" column="id" generator="native" setter="false";
8
9
// Properties
10
property name="name" ormtype="string";
11
property name="age" ormtype="numeric";
12
property name="lastVisit" ormtype="timestamp";
13
14
// Validation
15
this.constraints = {
16
// Example: age = { required=true, min="18", type="numeric" }
17
};
18
19
// Constructor
20
function init(){
21
super.init( useQueryCaching="false" );
22
return this;
23
}
24
}
Copied!

Setup for BDD

Since we love to promote tests at Ortus, let's configure our test harness for ORM testing. Open the /tests/Application.cfc and add the following code to setup the ORM and some functions for helping us test.
/tests/Application.cfc
1
// Locate the cborm module for events
2
this.mappings[ "/cborm" ] = rootPath & "modules/cborm";
3
4
// ORM Settings + Datasource
5
this.datasource = "coldbox"; // The default dsn name in the ColdBox scaffold
6
this.ormEnabled = "true";
7
this.ormSettings = {
8
cfclocation = [ "models" ], // Where our entities exist
9
logSQL = true, // Remove after development to false.
10
dbcreate = "update", // Generate our DB
11
automanageSession = false, // Let cborm manage it
12
flushAtRequestEnd = false, // Never do this! Let cborm manage it
13
eventhandling = true, // Enable events
14
eventHandler = "cborm.models.EventHandler", // Who handles the events
15
skipcfcWithError = true // Yes, because we must work in all CFML engines
16
};
17
18
public boolean function onRequestStart( string targetPage ){
19
ormReload();
20
return true;
21
}
Copied!
Now that we have prepared the test harness for ORM testing, let's test out our Person with a simple unit test. We don't over test here because our integration test will be more pragmatic and cover our use cases:
/tests/specs/unit/PersonTest.cfc
1
component extends="coldbox.system.testing.BaseTestCase"{
2
3
function run(){
4
describe( "Person", function(){
5
it( "can be created", function(){
6
expect( getInstance( "Person" ) ).toBeComponent()
7
});
8
});
9
}
10
11
}
Copied!

Basic CRUD

We will now generate a handler and do CRUD actions for this Person:
1
coldbox create handler
2
name="persons"
3
actions="index,create,show,update,delete"
4
views=false
Copied!
This creates the handlers/persons.cfc with the CRUD actions and a nice index action we will use to present all persons just for fun!
Please note that this also generates the integrations tests as well under /tests/specs/integration/personsTest.cfc

Create

We will get an instance of a Person, populate it with data and save it. We will then return it as a json memento. The new() method will allow you to pass a struct of properties and/or relationships to populate the new Person instance with. Then just call the save() operation on the returned object.
1
/**
2
* create a person
3
*/
4
function create( event, rc, prc ){
5
prc.person = getInstance( "Person" )
6
.new( {
7
name : "Luis",
8
age : 40,
9
lastVisit : now()
10
} )
11
.save();
12
return prc.person.getMemento( includes="id" );
13
}
Copied!
You might be asking yourself: Where does this magic getMemento() method come from? Well, it comes from the mementifier module wich inspects ORM entities and injects them with this function to allow you to produce raw state from entities. (Please see: https://forgebox.io/view/mementifier)

Read

We will get an instance according to ID and show it's memento in json. There are many ways in the ORM service and Active Entity to get objects by criteria,
1
/**
2
* show a person
3
*/
4
function show( event, rc, prc ){
5
return getInstance( "Person" )
6
.get( rc.id ?: 0 )
7
.getMemento( includes="id" );
8
}
Copied!
In this example, we use the get() method which retrieves a single entity by identifier. Also note the default value of 0 used as well. This means that if the incoming id is null then pass a 0. The orm services will detect the 0 and by default give you a new Person object, the call will not fail. If you want your call to fail so you can show a nice exception for invalid identifiers you can use getOrFail() instead.
1
/**
2
* show a person
3
*/
4
function show( event, rc, prc ){
5
return getInstance( "Person" )
6
.getOrFail( rc.id ?: -1 )
7
.getMemento( includes="id" );
8
}
Copied!

Update

Now let's retrieve an entity by Id, update it and save it again!
1
/**
2
* Update a person
3
*/
4
function update( event, rc, prc ){
5
prc.person = getInstance( "Person" )
6
.getOrFail( rc.id ?: -1 )
7
.setName( "Bob" )
8
.save();
9
return prc.person.getMemento( includes="id" );
10
}
Copied!

Delete

Now let's delete an incoming entity identifier
1
/**
2
* Delete a Person
3
*/
4
function delete( event, rc, prc ){
5
try{
6
getInstance( "Person" )
7
.getOrFail( rc.id ?: '' )
8
.delete();
9
// Or use the shorthnd notation which is faster
10
// getIntance( "Person" ).deleteById( rc.id ?: '' )
11
} catch( any e ){
12
return "Error deleting entity: #e.message# #e.detail#";
13
}
14
15
return "Entity Deleted!";
16
}
Copied!
Note that you have two choices when deleting by identifier:
    1.
    Get the entity by the ID and then send it to be deleted
    2.
    Use the deleteById() and pass in the identifier
The latter allows you to bypass any entity loading, and do a pure HQL delete of the entity via it's identifier. The first option is more resource intensive as it has to do a 1+ SQL calls to load the entity and then a final SQL call to delete it.

List All

For extra credit, we will get all instances of Person and render their memento's
1
/**
2
* List all Persons
3
*/
4
function index( event, rc, prc ){
5
return getInstance( "Person" )
6
// List all as array of objects
7
.list( asQuery=false )
8
// Map the entities to mementos
9
.map( function( item ){
10
return item.getMemento( includes="id" );
11
} );
12
}
Copied!
That's it! We are now rolling with basic CRUD cborm style!

BDD Tests

Here are the full completed BDD tests as well
/tests/specs/integration/personsTest.cfc
1
component extends="coldbox.system.testing.BaseTestCase" appMapping="/"{
2
3
function run(){
4
5
describe( "persons Suite", function(){
6
7
aroundEach( function( spec ) {
8
setup();
9
transaction{
10
try{
11
arguments.spec.body();
12
} catch( any e ){
13
rethrow;
14
} finally{
15
transactionRollback();
16
}
17
}
18
});
19
20
it( "index", function(){
21
var event = this.GET( "persons.index" );
22
// expectations go here.
23
expect( event.getRenderedContent() ).toBeJSON();
24
});
25
26
it( "create", function(){
27
var event = this.POST(
28
"persons.create"
29
);
30
// expectations go here.
31
var person = event.getPrivateValue( "Person" );
32
expect( person ).toBeComponent();
33
expect( person.getId() ).notToBeNull();
34
});
35
36
it( "show", function(){
37
// Create mock
38
var event = this.POST(
39
"persons.create"
40
);
41
// Retrieve it
42
var event = this.GET(
43
"persons.show", {
44
id : event.getPrivateValue( "Person" ).getId()
45
}
46
);
47
// expectations go here.
48
var person = event.getPrivateValue( "Person" );
49
expect( person ).toBeComponent();
50
expect( person.getId() ).notToBeNull();
51
});
52
53
it( "update", function(){
54
// Create mock
55
var event = this.POST(
56
"persons.create"
57
);
58
var event = this.POST(
59
"persons.update", {
60
id : event.getPrivateValue( "Person" ).getId()
61
}
62
);
63
// expectations go here.
64
var person = event.getPrivateValue( "Person" );
65
expect( person ).toBeComponent();
66
expect( person.getId() ).notToBeNull();
67
expect( person.getName() ).toBe( "Bob" );
68
});
69
70
it( "delete", function(){
71
// Create mock
72
var event = this.POST(
73
"persons.create"
74
);
75
// Create mock
76
var event = this.DELETE(
77
"persons.delete", {
78
id : event.getPrivateValue( "Person" ).getId()
79
}
80
);
81
expect( event.getRenderedContent() ).toInclude( "Entity Deleted" );
82
});
83
84
});
85
86
}
87
88
}
89
Copied!
Last modified 2yr ago