In my last post on relationships in Transfer, I talked about the ManyToOne relationship. I defined the Department object and associated employees with a department.
For today's post, I'll be talking about the ManyToMany relationship. This relationship exists when you have multiple connections between two objects. The example the Transfer docs give, and the I think that makes the best sample (especially considering where you are reading this), is the idea of blog entries and categories. A blog entry can have more than one category. On the database side, this is done via a join table with one column pointing to a blog entry and another column pointing to a category.
For the Employee Directory, I had a hard time thinking of a good way to add an example of a many to many relationship. I then thought of benefits. Typically everyone at a company gets the same set of benefits (dental, vision, medical, etc), but maybe at my fictitious company we let employees decide what benefits they want (perhaps each one takes a different amount of money out of your check). I began by creating a Benefit object:2 <id name="id" type="numeric" />
3 <property name="name" type="string" />
4 </object>
When it comes to a ManyToMany relationship, the first choice you have to make is which object will define the relationship. In other words, should I define the relationship in Benefit or Employee? For me, it seems like Employee is the 'primary' or 'most important' object, so I figured that's where I should define the connection. You should note that you will not be able to go in both directions in Transfer. By that I mean, you know I'll be showing you soon how to get related data. Once we do that though we will only be able to get data related to that object, not the other way around. I can see me needing to get benefits for an employee. I can't see me needing to get employees for a benefit.
I added this to my Employee definition:
2 <link to="employee" column="employeeidfk"/>
3 <link to="benefit" column="benefitidfk"/>
4 <collection type="array">
5 <order property="name" order="asc"/>
6 </collection>
7 </manytomany>
2 <cfdump var="#emp.getMemento()#">
3 <cfdump var="#emp.getBenefitsArray()#">
Alright - time to get cracking. I jumped back into my Employee edit form and began to add support for assigning benefits. I began by adding a line of code to get all the benefits. I'll use this for my drop down.
2 <cfif arrayLen(benes)>
3 <cfset oldbenelist = "">
4 <cfloop index="b" array="#benes#">
5 <cfset oldbenelist = listAppend(oldbenelist, b.getID())>
6 </cfloop>
7 <cfparam name="form.benefits" default="#oldbenelist#">
8 <cfelse>
9 <cfparam name="form.benefits" default="">
10 </cfif>
2 <cfloop query="benefits">
3 <option value="#id#" <cfif listFind(form.benefits, id)>selected</cfif>>#name#</option>
4 </cfloop>
5 </select>
2 <cfset employee.clearBenefits()>
3
4 <cfloop index="b" list="#form.benefits#">
5 <cfset benefit = application.transfer.get("benefit", b)>
6 <cfset employee.addBenefits(benefit)>
7 </cfloop>
2 #emp.getFirstName()# #emp.getLastName()# has the following benefits:<br/>
3 <ul>
4 <cfset benefits = emp.getBenefitsArray()>
5 <cfloop index="b" array="#benefits#">
6 <li>#b.getName()#</li>
7 </cfloop>
8 </ul>
9 </cfoutput>


Comment 1 written by Brian on 11 November 2008, at 7:08 AM
There is a case, my boss just called and it is budget cutting time. We need to see a list of who has each benefit to determine if it is worth keeping it.
Would you have to write a regular query at this point?
Comment 2 written by Raymond Camden on 11 November 2008, at 8:25 AM
Comment 3 written by Josh Grauer on 22 January 2009, at 1:42 PM
I've been going through your Transfer series of posts and they are excellent. I wanted to post one snag I ran into -- you mention this in your post:
"We begin with the manytomany tag. I give it a name and a table name. As with other declarations, the table name is optional if it matches the name."
I ran into an error when I didn't declare the table parameter. I got back a validation error saying that table was required per the transfer.xsd. I'm not sure if you would still get the error if you weren't linking to the xsd, but I thought I would mention it. I'm using Transfer 1.1.
Comment 4 written by Jeff on 23 January 2009, at 2:19 PM
I was having fun and learning too and you went and snuck in a CFMX8'ism, that using an array as an attribute to a CFLOOP tag at line 24 of the Index.cfm file.
Now I have to stop and think how I'm going to convert the array back to a list, and have to include that code for each new download...
Still, thanks, this kind of progressive build-up of an application using Transfer is just what I needed and hadn't found till you started it. and as an aside, I'm trying to get Transfer under my belt to be able to have a hope of coming up to speed on ColdBox, my next learning curve to climb!
Comment 5 written by Raymond Camden on 23 January 2009, at 2:22 PM
<cfloop index="b" array="#benes#">
<cfset oldbenelist = listAppend(oldbenelist, b.getID())>
</cfloop>
to
<cfloop index="x" from="1" to="#arrayLen(benes)#">
<cfset b = benes[x]>
<cfset oldbenelist = listAppend(oldbenelist, b.getID())>
</cfloop>
ColdBox? Don't you mean Model-Glue? ;)
Comment 6 written by Raymond Camden on 23 January 2009, at 2:25 PM
Comment 7 written by Jeff on 23 January 2009, at 3:23 PM
<code>
<cfloop index="b" from="1" to="#arrayLen(benefits)#">
<li>#Benefits[b].getName()#</li>
</cfloop>
</code>
Comment 8 written by Mark Mandel on 23 January 2009, at 4:25 PM
http://docs.transfer-orm.com/wiki/Transfer_Configu...
It doesn't say that it is optional, but it probably should be.
I'll log it as an enhancement.
[Add Comment] [Subscribe to Comments]