Metro 0.8.2 Now Available

The latest release for Metro (0.8.2) is now available. This release sees the addition of another service method and some significant updates to the service api.

create
The new create returns a populated and validated Transfer object. If the object passes validation, it is persisted. The default validation context is "all". Here is the method.

<cffunction name="create" access="public" output="false" returntype="any" hint="I return a populated and validated Transfer object.">
<cfargument name="objectName" type="string" required="true" hint="The Transfer object name.">
<cfargument name="input" type="struct" required="true" hint="The input struct of key/value pairs.">
<cfargument name="context" type="string" required="false" hint="The validation context." default="all">
<cfset var obj = new(objectName: arguments.objectName)>
<cfset obj.populate(args: arguments.input)>
<cfif obj.validate(context: arguments.context)>
<cfset getGateway(arguments.objectName).save(obj)>
</cfif>
<cfreturn obj>
</cffunction>

save
The save method now also returns a populated and validated Transfer object. If the object passes validation, it is persisted. The default validation context for save() is "all". The difference between create and save is that the former populates and validates a new object, whereas the latter "gets" the object using the input struct as the key (get will intelligently build the correct key from the input struct) and uses clone(). Here is the method.

<cffunction name="save" access="public" output="false" returntype="any" hint="I return a populated and validated Transfer object.">
<cfargument name="objectName" type="string" required="true" hint="The Transfer object name.">
<cfargument name="input" type="struct" required="true" hint="The input struct of key/value pairs.">
<cfargument name="context" type="string" required="false" hint="The validation context." default="all">
<cfset var obj = get(objectName: arguments.objectName, key: arguments.input).clone()>
<cfset obj.populate(args: arguments.input)>
<cfif obj.validate(context: arguments.context)>
<cfset getGateway(arguments.objectName).save(obj)>
</cfif>
<cfreturn obj>
</cffunction>

You can probably guess that I use create when trying to persist a new object and save for an update. Let's look at a sample coldbox controller that uses create or save.

<cffunction name="doSave" access="public" returntype="void" output="false">
<cfargument name="Event" type="any">

<cfscript>
var obj = "";
var context = "";
var message = "";
if (event.getValue("UserId",0) == 0) {
// create
context = "new";
message = "User successfully Added.";
obj = getUserService().create(objectName: "User", input: event.getCollection());
} else {
// save
context = "edit";
message = "User successfully Edited.";
obj = getUserService().save(objectName: "User", input: event.getCollection());
}
if (!obj.hasErrors()) {
setMessage(type: "success", message: message);
setNextEvent("user.list");
} else {
event.setValue("User",obj);
setNextEvent(event: "user." & context, persist: "User");
}
</cfscript>
</cffunction>

In the past, the save() method returned a result with a success flag, an array of errors and the obj as a payload that you could access via getResult(). I have since moved to maintaining the errors within the Transfer object, which renders a result object useless. Thus the change to the save() method.

list
The list method now accepts the "orderBy" and "asc" arguments to provide better control of simple list operations. Here is the list() mthod.

<cffunction name="list" access="public" output="false" returntype="query" hint="I return a query by object name and filter.">
<cfargument name="objectName" type="string" required="true" hint="The Transfer object name.">
<cfargument name="filter" type="struct" required="false" default="#StructNew()#" hint="The input filter of key/vlaue pairs.">
<cfargument name="orderBy" type="string" required="false" default="" hint="The optional property name to order by.">
<cfargument name="asc" type="boolean" required="false" default="true" hint="The boolean flag to specify whether to sort ascending or descending.">
<cfreturn getGateway(arguments.objectName).list(filter:arguments.filter, orderBy:arguments.orderBy, asc:arguments.asc)>
</cffunction>

If the filter param contains properties found in the object class, the gateway will use transfer's listByPropertyMap(), instead of list().

Finally, the create(), save() and delete() methods have the Transfer transaction advice applied to them. This allows one to write more complex methods that are transaction safe.

5 responses so far ↓

John Whish - Mar 23, 2009 at 4:28 AM

Nice work Paul! Can you use the "onMissingMethod" syntax of createUser( event.getCollection() ) like you can with new, list etc?

Paul Marcotte - Mar 23, 2009 at 1:33 PM

Hi John,

Yes, the virtualized method create{objectName}(argumentCollection:event.getCollection()) exists. Note that when using the onMissingMethod api for create, the default validation context "all" will be used.

Ilya Fedotov - Apr 4, 2009 at 5:04 PM

Hello, just found your metro framework and looking through it. I got where almost all unit tests pass.
there is one test that doesn't pass loginUserReturnsSuccessForGoodCredentials in SecurityServiceTest.cfc

The DELETE statement conflicted with the REFERENCE constraint "FK_audit_has_user". The conflict occurred in database "metro", table "dbo.tbl_audit", column 'user_id'.

Seems like there are other tests that do same thing, add user, test, delete user, with no problem. I am still digging but thought I would ask. Thanks.

Paul Marcotte - Apr 6, 2009 at 8:40 AM

Hi Ilya,

The error is related to the auditing that is taking place in the tests. If you comment out the bean definition for TransferAuditObserver in the /metro/tests/config/beans.xml file, you can avoid that. I cannot reproduce that error locally. Out of curiosity, are you running simultaneous tests?

Ilya Fedotov - Apr 6, 2009 at 3:02 PM

at first I ran all tests if that what you mean by simultaneous. But then just that one method.

UserServiceTest does almost same thing and it's working fine.

So I found something interesting, if I add variables.SecurityService.logoutUser(); at the end of loginUserReturnsSuccessForGoodCredentials in SecurityServiceTest.cfc it passes. Actually the test probably passed before but I don't get the error anymore when teardown() tries to delete user. I am not quite sure what that means, since that just deletes the user from session, so somehow the foreign key constrain is ok now? Doesn't quite make sense. Let me know if that gives you any ideas. I am still looking through the code and XML and all. Kind of tough without documentation.. wink, wink... :-) Seriously though thanks for publishing this some good ideas here imho.

Also I am on mssql 2005 if that makes a difference.

Leave a Comment

Leave this field empty: