cfWheels Relationships (part 3)

Let’s look at an example call to a contact, but without any of it’s relationships.

contact=model("contact").findOne(where="id=1");

So, the simplest possible call – find me one contact row where id = 1.

Well, it’s good to see our createdAt field got autofilled, and our calculated property of fullname made it through too. deletedAt is null, which is also good. The above call would return false if deletedAt contained a value.

Let’s do the same call, but get those email addresses:

contact=model("contact").findOne(where="id=1", include="emailaddresses");

That’s better! Incidentally, if there were no email addresses, we’d get an empty array like so:

Let’s extend this to the other relationships:

contact=model("contact").findOne(where="id=1",
		include="emailaddresses,contactCompanies,contactTechnologies");

So here we can see the join table relationship nested properties too. Now, here’s the catch. How do we get the data from the otherside of the join?

We can’t do this, as we’re returning a single entity with objects:

contact=model("contact").findOne(where="id=1",
include="emailaddresses,contactCompanies(company),contactTechnologies(technology);

We *can* do it if we return this record as a query (which is the default for a ‘findAll’ call):

contact=model("contact").findAll(where="id=1",
include="emailaddresses,contactCompanies(company),contactTechnologies(technology),
returnAs="query");

However, whether you actually want to do this will be dependent on your application. Sometimes, this will make perfect sense, especially if you’re calling data in any sort of tabular fashion. But if you’re calling your model in order to populate a form (for example) then this complicates matters. More on ‘hasManyCheckBox’ and associated solutions later.

cfWheels Relationships (part 2)

Now it’s just a matter of representing these relationships in cfWheels so we can create/update/delete some data.

models/Contact.cfc

<cfcomponent extends="Model" output="false">
    <cffunction name="init">
		<cfscript>
                // Model init functions
		property(name="firstname", label="First Name");
		property(name="lastname", label="Last Name");

		//Calculated Properties
		property(name="fullname", sql="CONCAT(firstname, ' ', lastname)");

		// Relationships
		hasMany(name="emailaddresses", dependent="deleteAll");
		hasMany(name="contacttechnologies", dependent="deleteAll");
		hasMany(name="contactcompanies", dependent="deleteAll");

		// Nested properties
		nestedProperties(associations="
                      emailaddresses,
                      contacttechnologies,
                      contactcompanies", allowDelete=true);

        </cfscript>
    </cffunction>
</cfcomponent>

So I’ve done a couple of things here worthy of note: I’ve set some default values for the firstname and lastname properties – namely the label name: I want this to display as ‘First Name’ rather than the default ‘firstname’ which is what the label=”” attribute is for. I’ve also added a very simple calculated property, of fullname, which takes the first and last names and puts them together at a database level. You can imagine how this can become very useful indeed – if I had prefix, suffix, middlename columns, I could build up various additional properties using those too.

Lastly, you’ll see the relationships & nested properties; emailaddresses has the ‘dependent=”deleteAll” attribute, which means when I delete a contact, the dependent email addresses will also be deleted. As the other relationships are also dependent to that contact, they’ll be deleted too – but bear in mind, this won’t delete the ‘source’ data, i.e the technologies or companies tables which is crucial as these are being used by other entities in the application. The nested properties call allows us to literally ‘nest’ models *within* the contacts model. More on this later.

models/Emailaddress.cfc

<cfcomponent extends="Model" output="false">
    <cffunction name="init">
		<cfscript>
        // Model init functions
		 belongsTo("contact");
        </cfscript>
    </cffunction>
</cfcomponent>

As this is our simplest relationship, an Email address is very easily represented with a simple ‘belongsTo’ call.

models/Technology.cfc

<cfcomponent extends="Model" output="false">
    <cffunction name="init">
		<cfscript>
		// Relationships
		hasMany(name="contacttechnologies");
        </cfscript>
    </cffunction>
</cfcomponent>

As this is ‘on the other end’ of the join for contact-technologies, we need to refer the technology model back to the join model.

models/Technology.cfc

<cfcomponent extends="Model" output="false">
    <cffunction name="init">
		<cfscript>
       	// Relationships
		belongsTo(name="contact");
		belongsTo(name="technology");
        </cfscript>
    </cffunction>
</cfcomponent>

And here is the join model – it ‘belongsTo’ both contact.cfc and technology.cfc and allows us to join the two.

models/Company.cfc

<cfcomponent extends="Model" output="false">
    <cffunction name="init">
		<cfscript>
		// Relationships
		hasMany(name="contactcompanies");
        </cfscript>
    </cffunction>
</cfcomponent>

models/Contactcompany.cfc

<cfcomponent extends="Model" output="false">
    <cffunction name="init">
		<cfscript>
       	// Relationships
		belongsTo(name="contact");
		belongsTo(name="company");
        </cfscript>
    </cffunction>
</cfcomponent>

Lastly, our company and contactcompany models.

Visualising the Models

I tend to find being able to visualise the data structure more useful than relying solely on code. What do these models look like if we just create a new one?

Let’s take the Contacts model, and simply call:

contact=model("contact").new();

The result?

As expected, but… what about our email addresses?  How do we get them into our nice new contact call?

We need to create a new instance of the emailaddress model, and pass it to the .new() call as a named argument. As there may be more than one email address, we need to put our email address model within an array, so cfWheels can keep track of multiple nested properties.

If we try:

newEmailaddress[1]=model("emailaddress").new();
contact=model("contact").new(emailaddresses=newEmailaddress);

We get:

So we now have an (empty) nested email address model within the new contact. Handy.

We can also extend this concept for the other relationships:

newEmailaddress[1]=model("emailaddress").new();
newContactCompany[1]=model("contactcompany").new();
newContactTechnology[1]=model("contacttechnology").new();

contact=model("contact").new(
	emailaddresses=newEmailaddress,
	contactCompanies=newContactCompany,
	contactTechnologies=newContactTechnology
);

Well, this is all very well, but what do these models look like with some data in them?

cfWheels Relationships (part 1)

No, this isn’t going to be a guide about getting on with your loved one (unless of course, your loved one is a certain open source coldFusion framework) but will specifically look at all the different types of relationships in cfWheels, and what they mean in terms of real world application design. I’m going to assume you have a working knowledge of cfWheels (objects/models/controllers/views/routes etc) as the documentation over at cfwheels.org is very good, and well worth digging into.

For this series, I’m going to take the old default example application of a ‘people’ database (or in this context, a contacts database).

Normalising Data

The old addage ‘DRY’ (or Don’t Repeat Yourself) applies just as much to your data, as it does to your controller & view code. I don’t consider myself a DBA by any stretch of the imagination, but it simply makes sense – don’t duplicate data (except in extreme circumstances – there I said it). With that in mind, let’s have a think about what this application should do, and how this might reflect our model conventions and model relationships.

The Contact model:

Well, at it’s core, a contacts directory needs, yes, you guessed it, a list of people. Let’s call them ‘Contacts’. At a mimimum, we’re going to need a contact.cfc model. This should contain any information which is unique to a contact, and has one value.

The table ‘contacts’ might look like this:

contacts
—————-
id [key, int, autoincrement]
firstname [varchar, 255]
lastname [varchar, 255]
createdAt [datetime]
updatedAt [datetime]
deletedAt [datetime]

Pretty simple – an id to reference with, firstname & lastname fields which we can add a calculated property to later to get a ‘fullname’ value, and three cfWheels specific fields, which will auto update – when it’s created, updated or deleted. Naturally, our id field will need to autoincrement, and be an integer.

So what about that contact’s data? Let’s assume we want to store the following data about this contact:

  • One or more email addresses, which are specific to that contact. This is a ‘one to many‘ relationship – one contact, multiple email addresses. It could be said a contact ‘hasMany‘ email addresses, and that those email addresses ‘belongTo‘ that contact.
  • A set of related technologies such as ‘mysql’, ‘php’, ‘coldFusion’, ‘cfWheels’. A contact might have many of these, and likewise, ‘mysql’ might need to refer to multiple contacts. So whilst a contact ‘hasMany’ technologies, they’re not exclusive to that contact.
  • A company relationship, such as ‘Adobe’, ‘Microsoft’, ‘Apple’ – we’d want to reuse these companies for other contacts too. Companies might also contain additional data such a postal address. We’d also want to store data about that relationship to that company – i.e a contacts postition, and be able to easily retrieve data about that company when looking at a contact.

Let’s take all these in turn, as each presents different challenges when referring back to our contact.

One or more email addresses: So a new table is called for to store our email addresses.

emailaddresses
——————-
id [key, int, autoincrement]
email [varchar, 255]
createdAt [datetime]
contactid [int]

As before, an id to reference with, an email column to store the actual value, and a createdAt so we know when it was added. To me, this data is less important to keep track of in terms of audit, so I’m ignoring the updatedAt/deletedAt fields. The contactid field here is the (excuse the pun) ‘key’ to the whole excercise. This ties in the email address to the contact, after we setup our model relationships later. This is probably the simplest relation type in this example.

A set of related technologies: We’ll need a table to store our ‘technologies’ – this will be a very simple table.

technologies
—————
id [key, int, autoincrement]
name [varchar, 255]

That’s all we need to store this, it’s essentially a glorified list of categories. But how do we store which contact has a technology, and which technologies refer to what contacts? We need a join table, which will simply be the referring id’s of the contact and the technology.

contacttechnologies
—————
contactid [key, int]
technologyid [key, int]

Note how I’ve named the table – and also the order of the columns. As “contact” has come first in the column name, I’ve made sure the column order reflects this – the reason why becomes obvious later on. As both columns are keys, this means we can’t have any duplicate rows – that’s a good thing – we don’t want random duplicate relationships popping up anywhere.

So this is what it looks like so far:

Company relationships

Our company relationships mean we need two things – one, a place to store our companies, and two, a join table to store the relationship in the same fashion as contacts – technologies as before.

companies (this is the same table structure as contacts for this example)
—————-
id [key, int, autoincrement]
name [varchar, 255]
createdAt [datetime]
updatedAt [datetime]
deletedAt [datetime]

contactcompanies
—————
contactid [key, int]
companyid [key, int]

This pattern should start to look a little familiar – it’s basically the same as the email address – contact relationship.

What does our db structure look like now?

Next, we’re going to look at representing these relationships within cfWheels.