Sam Farmer head shot

Sam Farmer

Growing up I never imagined I would play bass guitar for the Dave Matthews Band. And indeed it never happened.

But I have become a passionate and pretty good web developer.


Slides and Code: 5 Ways To Improve Your App with ORM

Here are the slides and code from my Adobe MAX ColdFusion Unconference talk 5 Ways To Improve Your App with ORM:

And the code which will set up the Derby DSNs and tables needed.

Available as a download and from Github. After installing:

  1. Go to setUp.cfm
  2. Enter in your cfadmin password and click 'Make Datasources'
  3. Go back to setUp.cfm and click on 'Go to App'

All of the mini apps/examples I used in the demo will now work.

Simple Example of jQuery Templates Loading JSON data

jQuery templates are a cool way to tie a datasource (often json, but at least arrays of maps/structurscriptput. First we define a template which are placed within script tags so that the browser ignores the html inside it:

view plain print about
1<script id="artistsTemplate" type="text/x-jquery-tmpl">
2    <div><h4>#${artistid} ${firstname} ${lastname}</h4>${city} ${state}</div>
3</script>

jQuery templates will loop over the array of maps/structures and for each ${xxx} replace the it with the value of a key of the same name (xxx). Quick sample of the first two records of data:

view plain print about
1[
2 {"state":"CO","lastname":"Donolan","firstname":"Aiden","artistid":1,"city":"Denver"},
3 {"state":"CA","lastname":"Weber","firstname":"Austin","artistid":2,"city":"Berkeley"}
4]

The output from that we will place in the following div:

view plain print about
1<div id="artistsContent"></div>

Data will be loaded each time we click on the Load Data button:

view plain print about
1<div><button iscript">Load Data</button> <span id="lastLoad"></span></div>

Here is the jQuery that makes it all happen:

view plain print about
1<script type="text/javascript">
2$(document).ready(function(){
3$("#getData").click( function() {
4    $.getJSON("artistsRemote.cfc?method=getArtists&returnformat=json",
5        function(data){
6            //empty out the div that will hold the generated content
7            $("#artistsContent").empty();
8            //call the tmpl function, pass in the data and have it append to artistsContainer
9            $("#artistsTemplate").tmpl( data ).appendTo("#artistsContent");
10            //indicate when last loaded
11            var nowIs = new Date().toLocaleString();
12            $('#lastLoad').html( nowIs );
13        });
14});
15});
16</script>

And what it looks like:

Getting the json data from ColdFusion is pretty easy. Here is the full ArtistsRemote.cfc (pick one of the return statements!):

view plain print about
1component {
2remote function getArtists() {
3    return entityLoad( "Artists", {}, "artistid");
4    return ormExecuteQuery(" SELECT new map(artistid as artistid, firstname as firstname, lastname as lastname,
5        city as city, state as state ) FROM Artists ORDER BY artistid");
6}
7}

The first return statement will serialize the whole object, the second just the five properties we will display.

Application.cfc:

view plain print about
1component {
2    this.name = "templatesSample";
3    this.datasource = "cfartgallery";
4    this.ormEnabled = true;
5}

For this example I am letting ColdFusion introspect the database for all columns which makes Artists.cfc pretty small:

view plain print about
1component persistent="true" {
2}

Finally for reference here is the full html page:

view plain print about
1<html>
2<head>
3    <title>Template Fun</title>
4    <style type="text/css">
5        body { font-family: Arial; font-size: 0.8em; }
6        h4 { margin: 0.5em 0 0 0 }
7    </style>
8    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
9    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
10<script type="text/javascript">
11$(document).ready(function(){
12$("#getData").click( function() {
13    $.getJSON("artistsRemote.cfc?method=getArtists&returnformat=json",
14        function(data){
15            //empty out the div that will hold the generated content
16            $("#artistsContent").empty();
17            //call the tmpl function, pass in the data and have it append to artistsContainer
18            $("#artistsTemplate").tmpl( data ).appendTo("#artistsContent");
19            //indicate when last loaded
20            var nowIs = new Date().toLocaleString();
21            $('#lastLoad').html( nowIs );
22        });
23});
24});
25</script>
26</head>
27<body>
28
29<div><button id="getData">Load Data</button> <span id="lastLoad"></span></div>
30
31<script id="artistsTemplate" type="text/x-jquery-tmpl">
32    <div><h4>#${artistid} ${firstname} ${lastname}</h4>${city} ${state}</div>
33</script>
34
35<div id="artistsContent"></div>
36
37</body>
38</html>

Using structures/maps instead of arrays in ORM

By default an ORM relationship is an array. Using a structure (map for non-ColdFusion programmers) is very easy and provides nice options. Lets start with a simple relationship between of users and addresses.


User.cfc

view plain print about
1component persistent="true" {
2property name="userId" fieldtype="id" generated="always" generator="native";
3property name="name" ormtype="string";
4
5property name="address" fieldtype="one-to-many" cfc="address" fkcolumn="userId" inverse="true";
6}


Address.cfc

view plain print about
1component persistent="true" {
2property name="addressId" fieldtype="id" generated="always" generator="native";
3property name="type" ormtype="string";
4property name="street" ormtype="string";
5property name="city" ormtype="string";
6property name="state" ormtype="string";
7
8property name="user" fieldtype="many-to-one" cfc="user";
9}


A single user object with two addresses would look like this:


Now, if we want to find out if the user has a work address we need to loop through getAddress(). Thats ok but by converting the relationship to a struct its possible to then use structure functions. Here is the relationship in User.cfc defined to return a structure:

view plain print about
1property name="address" fieldtype="one-to-many" cfc="address" fkcolumn="userId" inverse="true"
2    type="struct" structkeycolumn="type";


A dump of the user object now looks like:


Which means we can use code like this, in particular line 3:

view plain print about
1<cfset u = entityLoad("user")>
2<cfloop array="#u#" index="user">
3    <cfif structKeyExists( user.getAddress(), "work" )>
4        <cfdump var="#user.getAddress()["work"]#">
5    </cfif>
6</cfloop>


For reference here is the Application.cfc to set up Hibernate/ORM:

view plain print about
1component
2{
3this.name="structsDemo";
4this.ormEnabled=true;
5this.datasource="cfartgallery";
6this.ormsettings = { dbcreate="update" };
7
8}

CF Dude, What's in the cache?

Over the weekend I needed to see what was in my cache. I whipped up a quick script to list everything in cache and from there:

  • be able to remove an item
  • view the content of an item (limited to top 10 records)
  • view the metadata of an item.
I've included the code below.

view plain print about
1<cfscript>
2writeOutput('<h2>CF Dude, What''s in the cache?</h2><div><a href="#cgi.script_name#">Reload</a><br><br></div>');
3
4param name="url.do" default="";
5
6switch (url.do) {
7    case "remove":
8        cacheRemove(url.item);
9        writeOutput("<div style='color: green; font-weight: bold'>Removed #url.item#</div>");
10    break;
11    case "view":
12        writeDump( var=cacheGet(url.item), label="Cache contents of: " & url.item, top=10 );
13    break;
14    case "meta":
15        writeDump( var=cacheGetMetadata(url.item), label="Metadata for: " & url.item );
16    break;
17}
18
19incache = cacheGetAllIds();
20arraySort(incache,"textnocase");
21
</cfscript>
22
23<cfoutput>
24<h3>Cache IDs</h3>
25
26    <table cellspacing="4">
27    <cfloop array="#incache#" index="c">
28        <tr>
29            <td>#c#</td>
30            <td><a href="#cgi.script_name#?do=remove&item=#c#">remove</a></td>
31            <td><a href="#cgi.script_name#?do=view&item=#c#">view</a></td>
32            <td><a href="#cgi.script_name#?do=meta&item=#c#">meta</a></td>
33        </tr>
34
35    </cfloop>
36    </table>
37
38<h3>Cache Properties</h3>
39<cfdump var="#cacheGetProperties()#">
40</cfoutput>

The one where I try ColdFusion 9 ORM secondary cache and decide its awesome

I've pretty much overlooked the secondary cache for ORM and, having now played with it, I've realized what a big feature and performance enhancer this is.

In short with secondary cache on ColdFusion can get data from ehCache instead of the database. Set up ColdFusion 9.0.1 and you can share entity data across both ColdFusion instances and servers!

Let backtrack and take a look at a basic set up:

Application.cfc
view plain print about
1component {
2this.datasource = "cacheFun";
3this.ormEnabled = true;
4this.ormSettings = {secondaryCacheEnabled=true, logsql=true};
5}

To turn on secondary cache set secondaryCacheEnabled to true (the default is false). In this example I have also turned logsql to true*. Now lets look at our persistent cfc:

User.cfc

view plain print about
1component persistent="true" cachename="userCache" cacheuse="transactional" {
2property name="id" column="id" ormtype="int" fieldtype="id" generated="always" generator="native";
3property name="firstname" ormtype="string";
4property name="lastname" ormtype="string";
5property name="version" fieldtype="version" datatype="int" ;
6
7function getLastName() {
8    return uCase (variables.lastname);
9}
10}

That really is it. The two cache attributes tell ColdFusion to use secondary cache for this data. From now on a call like:

view plain print about
1user = entityLoad("User", {id=1}, true);

will use the secondary cache when possible.

To help work out what secondary cache is doing I did two things in User.cfc:

  1. added a version field which would definitively show when Hibernate had updated the record (another very cool underused feature)
  2. added my own getter to verify that secondary cache returns data via the cfc and not just flat

Now there are four types of cacheuse, here are the differences:

  • transactional cache is updated on the update statement and the next request of the data will get it straight from cache and not run a select
  • nonstrict-read-write Cache is updated on the first select after data is updated or inserted
  • read-write I could not get this to work with caching (docs say its the worst performing anyway)
  • read-only Only works with data that will not be updated. Produces an error if you attempt to update or insert records. (Marc Esher wrote a long, funny blog entry about using secondary cache to cache for a read-only view)

I'm going to look more into secondary cache and hopefully come up with real examples but for now I know two things: 1) its very powerful, 2) its very easy to use.

* If ColdFusion is started from the Builder server panel the console tab will display all the SQL Hibernate produces. I used it to see when Hibernate went to cache and when the database.

Starting Solr for ColdFusion on a Mac

Here is a straightforward way to start Solr when running ColdFusion 9 on a Mac. If it is not running in the ColdFusion Administrator under Data & Services > ColdFusion Collections the following message will appear:

Unable to retrieve collections from any of the Search Services. Please verify that atleast one of ColdFusion Search Services is installed and running.

To get it running run the following two lines in Terminal:

view plain print about
1cd /Applications/JRun4/servers/cfusion/cfusion-ear/cfusion-war/WEB-INF/cfusion/solr
2./cfsolr start

Thats for multi-instance installs for ColdFusion 9 standard install it will be:

view plain print about
1cd /Applications/ColdFusion9/solr
2./cfsolr start

Within seconds Solr will be up and running and collections will show on the page above.

(As a quick note don't delete core0 collection as its needed to get everything up and running.)

Update to debugClean and an interesting use of URL scope

Two updates to debugClean:

1. Bug fixed
For some users the same results would not refresh on subsequent uses of debugClean. This was caused by the underlying browser in Eclipse not refreshing. You may be shocked to hear that the browser causing this problem was Internet Explorer! At first I tried adding another variable to the url that is created when debugClean in fired up (its handlers/index.cfm if you want to see). However, this caused error after error and I eventually deduced that only one URL variable could be passed.

To solve this I created the name of the URL variable with createUUID() like so:

view plain print about
1work.cfm?#createUUID()#=#urlEncodedFormat( theXML.event.ide.projectview.resource.XmlAttributes['path'] )#

and in work.cfm have the following code to read in that variable:
view plain print about
1<cfloop collection="#url#" item="u">
2    <cfset path = url[ u ]>
3</cfloop>

2. Check for application/javascript
This one has caught me out a few times. will work in every browser except one. That missing browser? Internet Explorer! Though, by now, I should know better than to use application/javascript and instead to use text/javascript. Anyhoo the default configuration of debugClean will know check for application/javascript. This was as simple as adding it to the array in _config.cfm:

view plain print about
1request.lookFor = ["cfdump","writeDump","cflog","writeLog","console.log","application/javascript"];

Go to the main project page to download the latest version.

How to use ORM and onSessionEnd()

At 16applications I use ORM extensively. Recently I wanted to track when users sessions timed out which the onSessionEnd function in Application.cfc is perfect for.

onSessionEnd is interesting in that its not part of a standard ColdFusion request process which ORM is somewhat dependent on. In order to use ORM inside it I had to use ormFlush() to get changes to flush to the database.

view plain print about
1function onSessionEnd( SessionScope ) {
2    if ( structKeyExists( arguments.SessionScope, "loginTrackerId" ) ) {
3        var lt = entityload("loginTracker", {loginTrackerId=arguments.SessionScope.loginTrackerId}, true );
4        lt.setOutDT( now() );
5        lt.setTimedOut( true );
6        ormFlush();
7    }
8}

Sharing Data Across Instances with objectSave() and objectLoad()

Running multiple ColdFusion instances is pretty awesome from failover to performance. Sharing data among them throws up a couple possibilities:

  • Use a database (save from one, retrieve from another)
  • Use ColdFusion 9.0.1 with Distributed Ehcache and then use the cachePut() and cacheGet() functions.
  • Use objectSave() and objectLoad() while saving the object to disk.

Recently I needed to do just this with a dataset of structures and arrays that could have changed at anytime. This made option 1 a little tricky. Option 2 was my favorite but required some extra installs with Ehcache (which is a simple 7 step process, but would have required testing, yada, yada). So, option 3 was what we picked and it worked well.

Here is a simple example of how to do it. First create and save your object:

view plain print about
1myObject = {
2    funRun = [1,3,5],
3    hardcore = [10,26,1000]
4};
5objectSave( myObject, "myObject.cfo");

The objectSave() function takes two arguments; first the object, and second the filename. By default it saves it relatively to the running file. The documentation use a ".out" extension but I think a ".cfo" is more fun (ColdFusion Object or if you need to please your CFO tell them you named it after them!).

Retrieval is just as easy:

view plain print about
1<cfset myObject = objectLoad("myObject.cfo")>

And that will work across any instance.

If you have large objects you may run into issues with writes and saves coming at the same time. In that case wrap the objectLoad() in a try/catch.

ORM and Dates? You probably want timestamp

Data types for dates and times are slightly different across databases which often makes life difficult for us developers. ColdFusion ORM makes this pretty simple:

  • For a date with time use ormtype="timestamp"
  • Just a date use ormtype="date"

There are no other options!

Full example with the property tag:

view plain print about
1property name="aFullDateWithTime" ormtype="timestamp";
2property name="justADate" ormtype="date";

More Entries

BlogCFC was created by Raymond Camden. This blog is running version 5.9.7. Contact Blog Owner