cfWheels Relationships (part 4)

So far we’ve setup the database tables and the models, so now we need to start thinking about getting and updating this contacts data, including the associated nested properties.

Outputting a simple add/edit form should hopefully be familiar to you by now (if not I recommend checking out the cfwheels screencasts: http://cfwheels.org/screencasts ). When updating our nested properties, there are several methods, each with pros & cons.

Firstly, let’s have a look at HasManyCheckBox();

The hasManyCheckbox() helper is really, really useful. I’m going to use our technologies relationship, as this is just the sort of data which is designed to be displayed/updated in this fashion.

#hasManyCheckBox(
    objectName="Contact",
    association="contactTechnologies",
        keys="#contact.key()#,#id#",
        label=name)#

Now what does this actually do? The loop itself should be obvious – we’re looping over *all* the potential values for technologies – after all, we want to be able to list those entries which aren’t ticked too: but we need to be able to make sure that the entries which are present in the join table are properly ticked. ObjectName=”Contact” is our parent object – the one which holds the nested properties. Association is the name of the nested property itself. Next, you have the most important part, the composite key. Remember when I mentioned about the column key order in the database construction for our join table? *This* is why it’s important: the order of the keys here *MUST* reflect the order in the database schema. I’ve used contact.key() which allows me to do this on a new model (which won’t actually have a primary key per se – yet).

So what does this wonderful piece of code actually output?

<label class="checkbox" for="Contact-contactTechnologies-1-1-_delete">ColdFusion<input id="Contact-contactTechnologies-1-1-_delete" type="checkbox" name="Contact[contactTechnologies][1,1][_delete]" value="0" checked="checked" /><input id="Contact-contactTechnologies-1-1-_delete-checkbox" type="hidden" name="Contact[contactTechnologies][1,1][_delete]($checkbox)" value="1" /></label>

<label class="checkbox" for="Contact-contactTechnologies-1-4-_delete">CSS<input id="Contact-contactTechnologies-1-4-_delete" type="checkbox" name="Contact[contactTechnologies][1,4][_delete]" value="0" /><input id="Contact-contactTechnologies-1-4-_delete-checkbox" type="hidden" name="Contact[contactTechnologies][1,4][_delete]($checkbox)" value="1" /></label>

<label class="checkbox" for="Contact-contactTechnologies-1-5-_delete">HTML5<input id="Contact-contactTechnologies-1-5-_delete" type="checkbox" name="Contact[contactTechnologies][1,5][_delete]" value="0" /><input id="Contact-contactTechnologies-1-5-_delete-checkbox" type="hidden" name="Contact[contactTechnologies][1,5][_delete]($checkbox)" value="1" /></label>

<label class="checkbox" for="Contact-contactTechnologies-1-6-_delete">jQuery<input id="Contact-contactTechnologies-1-6-_delete" type="checkbox" name="Contact[contactTechnologies][1,6][_delete]" value="0" /><input id="Contact-contactTechnologies-1-6-_delete-checkbox" type="hidden" name="Contact[contactTechnologies][1,6][_delete]($checkbox)" value="1" /></label>

etc. As you should be able to see from the above, there’s actually some fairly complex stuff going on: imagine it as cfWheels by default adding all the associations & relationships, then deleting all the relationships, then by checking a checkbox, you save that association from being deleted. Looking at the dumped params struct makes this more obvious:

The ‘1,1’, ‘1,2’ struct keys are the composite keys: 1 representing the contactid, and the second representing the technology (in this case). If the checkbox is checked, then the _DELETE sub struct returns false.

What’s nice about this approach is that you don’t have to do anything extra; when submitting and creating the final contact object, you just have to make sure you include the associations.

‘Why that’s awesome!’ I hear you say. Yes, yes it is. BUT. It doesn’t help us in a few areas.

1) We can’t add new values to the list easily – i.e I can’t add a new technology
2) We can’t edit the associated properties easily – i.e I can’t edit an existing technology

Of those two points, I would argue editing existing properties from within the contact update/add form is perhaps not the place for it – these properties could apply to other contacts, so pushing them out into their own form is probably better from a user perspective – there’s then no chance of the user thinking ‘oh I’ll just change ColdFusion to Railo’ and then the horror when all other contacts then list Railo too.

Adding new values to the technology list is however something I see happening. To do this, I’ve found the easiest solution is to just create a new form field tag, and then test for the values when updating the contact object.

This means when we submit our update or create request, we need to test for the existence of the field, return the created key, and then add the association manually.

I’ve found the easiest way to do this (so far) is to add it to a new private function: So our update function calls our new checking function ‘checkForNewTechnologies()’:

contact.update(params.contact);
	if(contact.hasErrors()){
         renderPage(action="edit");
	}
        else {
	checkForNewTechnologies();
	 flashInsert(success="The contact was updated successfully.");
	 redirectTo(action="view", key=contact.id);
}

Which looks like:

<cffunction name="checkForNewTechnologies" access="private">
<cfscript>
  if(structkeyexists(params, "newtechnology") AND len(params.newtechnology) GT 2) {
		nTechnology=model("technology").create(
			name=params.newtechnology
		  );
		nTechnologyAssociation=model("contacttechnology").create(
			contactid=contact.key(), technologyid=nTechnology.key()
		 );
  }
</cfscript>
</cffunction>

Obviously, this is just one way of many to do it. You could add the new technology via Ajax, return the generated ID, then replicate the form HTML via JS to pass the new value in along with the other checkboxes: this is quite a cumbersome technique as you rely on recreating the wheels generated HTML manually, but might have it’s uses.