Building CRUD with Transfer (2)

This post is more than 2 years old.

I'm a bit late in finishing up the next part of my Transfer series (I blame Fable 2 - it kept me up till 2AM!), but if you remember the last entry, I discussed how to create, update (save), delete, and get data using Transfer. I also showed how Transfer creates a CFC based on the XML definition we created for our Employees. This is really everything we need to build a simple employee editor, except for one piece - getting a list of employees.

I alluded to the fact that Transfer had many ways of getting data. Yesterday's post showed the simple get() method. Today we will look at list() and it's variants.

The list() method takes a class (the name of our data type) and an optional column to sort by. You can also pass a third argument that specifies if the sort should be an ascending sort or a descending sort. This one tends to trip me up. Instead of passing "asc" or "desc", you pass a true or false. True implies ascending and false means descending. (There is a four argument as well but we won't worry about that now.)

So all in all, to get a list of employees it takes all of one call.

<cfset employees = application.transfer.list("employee", "lastname")>

Simple, right? The list method returns a query of employees, not an array of Employee objects. So by now we easily have enough to build a tool to let us administrate employees. For this demo, I decided to put these files within an admin folder, but didn't bother to add any security. In case it isn't obvious, you wouldn't do that in production. I began with employees.cfm:

<cfif structKeyExists(url, "delete")> <cfset employee = application.transfer.get("employee", url.delete)> <cfset application.transfer.delete(employee)> </cfif>

<cfset employees = application.transfer.list("employee", "lastname")>

<h2>Employees</h2>

<cfif employees.recordCount> <table border="1"> <tr> <th>Name</th> <th>Email</th> <th> </th> </tr> <cfoutput query="employees"> <tr> <td><a href="employee.cfm?id=#id#">#lastname#, #firstname#</a></td> <td>#email#</td> <td><a href="employees.cfm?delete=#id#">Delete</a></td> </tr> </cfoutput> </table> <cfelse> <p> There are no employees now. </p> </cfif>

<p> <a href="employee.cfm">Add Employee</a> </p>

This follows my typical admin pattern of 'Get the crap, make a table, and link to edit or delete.' You can see the list command and the table that outputs each employee. I link to employee.cfm for editing, and back to myself for deleting. If you remember from yesterday, you have to get the TransferObject before you delete via Transfer.

Now let's look at employee.cfm, the main editor for employees:

<cfparam name="url.id" default="0">

<cfif url.id is 0> <cfset employee = application.transfer.new("employee")> <cfelse> <cfset employee = application.transfer.get("employee", url.id)> </cfif>

<!--- param values ---> <cfparam name="form.firstname" default="#employee.getFirstName()#"> <cfparam name="form.lastname" default="#employee.getLastName()#"> <cfparam name="form.dob" default="#employee.getDOB()#"> <cfparam name="form.email" default="#employee.getEmail()#"> <cfparam name="form.phone" default="#employee.getPhone()#">

<cfif structKeyExists(form, "save")> <cfset employee.setFirstName(form.firstname)> <cfset employee.setLastName(form.lastname)> <cfset employee.setDOB(form.dob)> <cfset employee.setEmail(form.email)> <cfset employee.setPhone(form.phone)> <cfset application.transfer.save(employee)> <cflocation url="employees.cfm" addtoken="false"> </cfif>

<h2>Edit Employee</h2>

<cfoutput> <form action="employee.cfm?id=#url.id#" method="post"> <table> <tr> <td>First Name:</td> <td><input type="text" name="firstname" value="#form.firstname#"></td> </tr> <tr> <td>Last Name:</td> <td><input type="text" name="lastname" value="#form.lastname#"></td> </tr> <tr> <td>Date of Birth:</td> <cfif isDate(form.dob)> <cfset v = dateFormat(form.dob)> <cfelse> <cfset v = ""> </cfif> <td><input type="text" name="dob" value="#v#"></td> </tr> <tr> <td>Email:</td> <td><input type="text" name="email" value="#form.email#"></td> </tr> <tr> <td>Phone:</td> <td><input type="text" name="phone" value="#form.phone#"></td> </tr> <tr> <td> </td> <td><input type="submit" name="save" value="Save"></td> </tr> </table> </form> </cfoutput>

When I'm not using a MVC based framework, I typically create self-posting forms like you see here. Starting at the top, we check to see if we are editing a new record or an existing one. Notice one calls new() and one calls get(). After I have the object I param my form fields based on all the properties of the Employee. Notice that I just check for a form submission, but don't bother validating. Thats a whole other topic and not really Transfer specific. The important line is here:

<cfset application.transfer.save(employee)>

I know I mentioned this yesterday, but it's rather cool that I don't have to worry about my object being new or old. I just save. Let Transfer worry about it. The rest of the template is a simple form and not terribly interesting.

All in all, Transfer looks pretty simple so far, wouldn't you agree? I like how I can easily get a query back without having to write the SQL myself and I certainly like the ease of use of the CRUD methods we discussed more in the previous entry. I never have to write one CFC - no beans or gateways.

Does this mean a Transfer user would never write CFCs? Of course not. I mentioned in the first entry in this series that I was intentionally avoiding Model-Glue for this application so I could keep it as simple as possible. Normally I would have a CFC in my component for Employees, but where I'd have a set of queries I'd simply use Transfer instead. In my last big Model-Glue application, I created a service CFC, a gateway CFC, a DAO CFC, and bean CFC for each type of data in the application. When I updated CFLib to use Model-Glue and Transfer, I pretty much ended up with a service CFC in model and that was it. Transfer took care of everything else! One thing that comes to mind about both Model-Glue and Transfer is - I really appreciate how one makes my life simpler by helping me lay out and organize my application structure and another makes my life simpler by abstracting away some of the more generic SQL and CFC operations I typically have to make. Together - both take care of routine things and let me focus more on the application at a high level.

Alright so where next? It would be nice if all web applications had only one type of data to deal with, but unfortunately that's not the case in the real world. Our Employee Directory won't just be a simple list of names, but a complex set of employees with relations to each other. In the next entry we will start with a new type of data, the department. I will then show how I can tell Transfer that each employee belongs to a department, and how we can make it easy to display an employee's department.

Download attached file.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Tim Garver posted on 11/7/2008 at 8:32 AM

Very nice Ray..

Comment 2 by Sean posted on 11/7/2008 at 8:32 AM

You can just do a get('Employee',url.id) no need to call new if id is zero. calling get with a non-existant ID returns an empty transferObject so its the same as calling new but without the cfif overhead.

Comment 3 by Raymond Camden posted on 11/7/2008 at 8:35 AM

@Sean: Good tip there! I didn't know that. Although to be honest, I prefer the more specific code here. On the flip side, I don't call create before my save since save does it for me - so in _that_ case I prefer the less specific code I guess. ;)

Comment 4 by Bob Silverberg posted on 11/7/2008 at 9:37 AM

Although it's true that transfer.get() will return an empty object if it doesn't find an existing object for a given id (which is a great feature, IMO), I'm not sure that it would be a good design decision in this case.

Transfer has no way of knowing that 0 is not a valid id, so it will query the database to find out. By using transfer.new() whenever you know that you want an empty object you can avoid that database access, which seems like it would save more overhead than a simple cfif.

Comment 5 by Sean posted on 11/7/2008 at 4:48 PM

Actually Transfer will call getIsPersisted to determine

Comment 6 by Bob Silverberg posted on 11/7/2008 at 7:16 PM

@Sean: I assume you're talking about how Transfer decides whether to create a new DB record or update an existing DB record when doing a save. Yes, it is able to determine that automatically based on the state of the object.

I was talking specifically about using get() vs. new() when creating a Transfer Object. If you run a quick test I believe you'll find that any time you call transfer.get("myclass",0) it will run a query against your database to see if 0 is a real record. Even if you do this several times within a single request (e.g., if you want to create multiple Line Items for an Order), if you use transfer.get() instead of transfer.new() it will run a query against the database each and every time.

Note that my use of 0 in the above example is just an example of a value that does not correspond to a valid record. There is nothing magical about 0 - if you do have a record in your table with an id of 0 it would simply retrieve that record, which is yet another reason to use transfer.new() instead of transfer.get() if you know you want an empty Transfer Object.

Comment 7 by Sean posted on 11/7/2008 at 11:17 PM

Good point.

Comment 8 by Tim Garver posted on 11/8/2008 at 12:41 AM

Awesome stuff...

Found a few issues I can't seem to work around.

Lets say you have an existing query you want to replace with a Transfer object.
In the query you have several Expressions in the select list as column aliases.
[code] select N.start_time + 5/24 + N.GMT_OFFSET/24 as ST,
N.* FROM table N where N.x=y
[/code]
How can this be accomplished via Transfer?

I have tried to make a property default but thats not allowed in the transfer.xml object.

Anybody have any ideas?

Great work Ray!!

Tim

Comment 9 by Raymond Camden posted on 11/8/2008 at 12:44 AM

Tim, this is going to be covered later. Transfer has a kick ass feature called Decorators that can do exactly that. If you don't want to wait, just look for Decorators in the official docs.

I'm not sure when I'll cover it in this series. I'm kinda playing things out on the fly. The plan now is:

Joins (2-3 blog entries on relating data)
Getting Data (more advanced then get/list, TQL)
Decorators
Events
Conclusion

So as you can see, it may be late next week until I hit it up, and MAX will definitely slow me down.

Comment 10 by Tim Garver posted on 11/8/2008 at 12:58 AM

ok cool,
i have a few of those now, but they are the basic ones my CFC Generator built for me which just has validation in them.

I tried TQL but it doesn't support
Aggregate functions
SQL Functions
Subselect in FROM statements.
Subselect in SELECT Column statements.

Still loving Transfer...

Tim

Comment 11 by Henry Ho posted on 11/8/2008 at 4:45 AM

Code Reuse: Taking Advantage of Includes and Overwrites in Transfer

http://www.quackfuzed.com/i...

Comment 12 by Jon Hayes posted on 5/20/2010 at 7:42 AM

Hi,
I need some help answering this question.
Does MG3 Gesture CRUD (as per the new docs wiki examples) remove the need to use Transfer ORM CRUD if one was building an app with MG3? Or would anyone still use transfer + MG3 to do this?