Sunday, July 25, 2010

Declaring a variable with same name as a column in cfloop

I have a query with a column name json in it, which may or maynot contain a json string. If it does contain json I want to deserialize it, put it into an array loop, and output the results. I want to use the same in a variable as the column coming from the query because it reads good. When I did this I noticed that you can't declare a variable with the same name...even if you prefix the query column.

Here's is my test case. We will break it down in a sec.

<cfset query = queryNew("id,json")/>

<cfset queryAddRow(query)/>
<cfset querySetCell(query,"id","1")/>
<cfset querySetCell(query,"json","")/>

<cfset array = [{id=1,name="Joe"},{id=2,name="Tony"},{id=3,name="Ryan"}]/>

<cfset queryAddRow(query)/>
<cfset querySetCell(query,"id","2")/>
<cfset querySetCell(query,"json",serializeJSON(array))/>

<cfloop query="query">

<cfset json = []/>

<cfif len(query.json) gt 0>
<cfset json = deserializeJSON(query.json)/>
</cfif>

<cfloop from="1" to="#arrayLen(json)#" index="i">
#i.name#
</cfloop>

</cfloop>


The above test case will error out. Because you can't loop the following because ColdFusion thinks that variable "json" is a string, even though I declared it as an array.

<cfloop from="1" to="#arrayLen(json)#" index="i">


First we make a query with a json string in it

<cfset query = queryNew("id,json")/>

<cfset queryAddRow(query)/>
<cfset querySetCell(query,"id","1")/>
<cfset querySetCell(query,"json","")/>

<cfset array = [{id=1,name="Joe"},{id=2,name="Tony"},{id=3,name="Ryan"}]/>

<cfset queryAddRow(query)/>
<cfset querySetCell(query,"id","2")/>
<cfset querySetCell(query,"json",serializeJSON(array))/>


Next we loop the query and check if there is a json string in the query column "JSON". I started an array with the same name as query column json. I did this so if there isn't any json after we deserialize the json it won't error when we try to loop the array.

<cfset json = []/>

<cfif len(json) gt 0>
<cfset json = deserializeJSON(query.json)/>
</cfif>


Lastly I try to loop the array "json" and output the data, but it errors out because it still is looking at the loop variable "json" not the variable I declared "json". Yes, I can solve this problem easily but changing my declared variable from "json" to "array" or something like that, but I wanted to be clear what I was loop...and I just wanted to use the variable name "json".

Saturday, July 24, 2010

ColdMVC: Flatten an array of objects with children.

In record based systems "non object based" if you wanted to store a parent/child relationship you usually stored a parent_id on the same table.

Example:

CMS example showing a page table where a page can have many pages underneath it.

Page table.
ID,Name,Address,Parent_ID
1,Products,products/,null
2,Tiles,products/tiles/,1

Then in order to simulate an object based system, you usually loop the query and put the records in structs of structs.

Example.

result = {
id="1",
name="Products",
address="products/",
children=[
{
id="1",
name="Products",
address="products/",
children=[]
}
]
}

Notice there is children an array.

Using ColdMVC we actually start with "struct of structs" or object based, instead of starting with a query. So we need to take the objects array, we are using array of objects because that is what is return from an hql query, and pull out the children and put them in the array, thus flattening the tree.

To do this I run it through a helper function I made below.

<cffunction name="flattenArrayTree" access="public" output="false" returntype="array">
<cfargument name="array" required="true"/>
<cfargument name="result" required="false" default="#[]#"/>
<cfargument name="treeDepth" required="false" default="0"/>
<cfargument name="childrenPropertyName" required="false" default="children" hint="A property with an array of child objects."/>

<cfset var local = {}/>
<cfset var i = ""/>

<cfloop from="1" to="#arrayLen(arguments.array)#" index="i">

<cfset local.object = arguments.array[i]/>

<cfset local.object.setTreeDepth(arguments.treeDepth)/>

<cfset arrayAppend(arguments.result,local.object)>

< !---make sure the "children" property exists is in the object--->
<cfif not structKeyExists(local.object,"set"&arguments.childrenPropertyName)>

<cfthrow detail="The argument childrenPropertyName which is currently #arguments.childrenPropertyName# does not exist as a property in the object"/>

< /cfif>

<cfif arrayLen(local.object._get(arguments.childrenPropertyName)) gt 0>

<cfset arguments.treeDepth++/>

<cfset arguments.result = flattenArrayTree(local.object._get(arguments.childrenPropertyName),arguments.result,arguments.treeDepth)/>

<cfset arguments.treeDepth--/>

< /cfif>

< /cfloop>

<cfreturn arguments.result/>

< /cffunction>

You will notice I add a property called "treeDepth". In order to use the function you need to add a property to the model cfc called "treeDepth". Don't worry about it being added to db, if it's not mapped in hibernate file it won't be added to the db. I use the treeDepth property to know how far in a child object is. Technically...you don't need this, but I find a very handy.

Monday, July 12, 2010

CF9 ormsetting dialect

I was just wondering why you need to specify a dialect in ORM, if you define this in your datasource?

Example:
Application.cfc
this.ormSettings = { datasource="jbankenBlogspot",dialect="MySQLwithInnoDB" };

I shouldn't have to specify dialect="MySQLwithInnoDB" if I did it already in my datasource. Maybe I am missing something here.

The real hard part is that hibernate was throwing an error that was the same as if you had a bad path to your entity cfc.

Wednesday, July 7, 2010

ColdMVC: Parse checkboxes or radios generically

I ran into an interesting issue awhile, back. I wanted to edit a product and click on checkboxes for one to many relationships to colors, categories, and sizes. When I post the form to the ProductController save() I wanted a generic way to convert checkbox values (which are ids) to actually objects. Below are the steps I took followed by the code.

First, I call private functions to parse the specific checkbox ids ( Ex. parseCategories()), but they all just call parseResource().

Next, while in parseResource() I look into the variables scope for the model (Ex. _Size) to dynamically get the object by using findByID().

Lastly, I append the object to an array and populate the Product object and save it.

ProductController.cfc

/**
* @accessors true
* @action list
* @extends coldmvc.Controller
*/
component {
property _Size;
property _Color;
property _Category;

function save() {

var product = _Product.new();

params.product.categories = parseCategories(params.product.categories);
params.product.sizes = parseSizes(params.product.sizes);
params.product.colors = parseColors(params.product.colors);

product.populate(params.product);

product.save();

redirect({controller="product",action="setup"},"productID=#product.id()#");
}

private array function parseCategories(string categoryIDs) {

return parseResource("Category",arguments.categoryIDs);

}

private array function parseSizes(string sizeIDs) {

return parseResource("Size",arguments.sizeIDs);

}

private array function parseColors(string colorIDs) {

return parseResource("Color",arguments.colorIDs);

}

private array function parseResource(string resource, string resourceIDs){

var resources = $.string.toArray(arguments.resourceIDs);

var result = [];
var i = "";

for (i=1; i <= arrayLen(resources); i++) {

arrayAppend(result, variables["_#arguments.resource#"].findByID(resources[i]));

}

return result;

}
}


I wanted to share this just in case someone else is running into the issue.