1

I have a many to many relationship setup, and need to control the order.

I have an entity with the property below. How do I control the order of the components when retrieved via this relationship?

property name="Components" fieldtype="many-to-many" cfc="CmsComponent" 
      type="array" 
      singularname="Component" 
      linktable="CMSPageComponents" 
      fkcolumn="page_id" 
      inversejoincolumn="component_id";

I've setup a column in the linktable (CMSPageComponents) called dispOrder. But when I set the orderby="dispOrder" or orderby="CMSPageComponents.dispOrder" attribute on the property above, it just seems to ignore it.

Any suggestions on how I can control the order of a many-to-many relationship?

Leigh
  • 28,765
  • 10
  • 55
  • 103
Jason
  • 1,957
  • 2
  • 20
  • 34
  • AFAIK many-to-many is actually not too widely used and quite limited. Try turning it into a one-to-many and sort order should be well supported. – Henry Nov 01 '13 at 00:15
  • Yes, one-to-many does have sorting quite easy, but I need a many-to-many. one-to-many just doesn't give me the functionality I need. – Jason Nov 01 '13 at 04:06
  • 1
    What functionality? A many-to-many is basically just a pair of one-to-many relationships. If you want orderby with a column on the linktable, afaik promoting it to an entity is what you need to do. – Peter Boughton Nov 01 '13 at 10:57

1 Answers1

3

When the relationship is many-to-many CF will apply the orderBy statement to the object table not to the link table. So in theory you could move your dispOrder column from the CMSPageComponents link table to the CmsComponent table to make it work.

But in practice I expect the ordering is specific to the many-to-many relationship (i.e. the particular page), in which case you could follow Peter's advice and create a separate entity which links the other two entities and lets you define an order property.

So you would have 3 entities:

  1. CmsPage
  2. CmsComponent
  3. CmsPageComponent

CmsPageComponent might look something like this:

<cfcomponent displayname="CmsPageComponent" persistent="true" table="cmsPageComponents">
    <!--- Add a primary key for the link Entity --->
    <cfproperty name="ID" fieldType="id" generator="native">
    <cfproperty name="dispOrder">
    <cfproperty name="page" fieldType="many-to-one" cfc="CmsPage" fkColumn="pageID">
    <cfproperty name="component" fieldType="many-to-one" cfc="CmsComponent" fkColumn="componentID">
    <!--- init() etc --->
</cfcomponent>

CmsPage could then have a one-to-many relationship with the link entity allowing ordering using the dispOrder column:

<cfcomponent displayname="CmsPage" persistent="true" table="cmsPages">
    <cfproperty name="ID" fieldType="id" generator="native">
    <cfproperty name="pageComponents" singularName="pageComponent" fieldType="one-to-many" cfc="PageComponent" fkColumn="pageID" orderBy="dispOrder">
    <!--- init() etc --->
</cfcomponent>

Update The following shows how you might add and display page components. Not the only or necessarily best way, but just to give you an idea:

<cfscript>
transaction{
    //load the page
    page    =   EntityLoadByPK( "CmsPage",1 );
    //load the components we want to add
    component1  =   EntityLoadByPK( "CmsComponent",1 );
    component2  =   EntityLoadByPK( "CmsComponent",2 );
    //create link objects
    pageComponent1  =   EntityNew( "CmsPageComponent" );
    pageComponent2  =   EntityNew( "CmsPageComponent" );
    // link them to the pages and components in the order we want
    pageComponent1.setComponent( component1 );
    pageComponent1.setPage( page );
    pageComponent1.setDispOrder( 2 );
    EntitySave( pageComponent1 );
    pageComponent2.setComponent( component2 );
    pageComponent2.setPage( page );
    pageComponent2.setDispOrder( 1 );
    EntitySave( pageComponent2 );
}
//Reload from the database so the order is applied
EntityReload( page );
</cfscript>
<!DOCTYPE html>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <cfoutput>
        <h2>Page #page.getID()#</h2>
        <ol>
            <cfloop array="#page.getPageComponents()#" index="pageComponent">
                <cfset component    =   pageComponent.getComponent()>
                <li>Component ID #component.getID()#, Display Order = #pageComponent.getDispOrder()#)</li>
            </cfloop>
        </ol>
    </cfoutput>
</body>
</html>

NOTE: this assumes the ORM setting flushAtRequestEnd is true in Application.cfc

CfSimplicity
  • 2,338
  • 15
  • 17
  • Thanks CFSimplicity!! Thanks to your explanation I think I understand what Peter was getting at now. But I still can't see how this can emulate a many-to-many. I can't think how I would add a component to a page, or get a list of components without it becoming a convoluted process? Am I better of just reverting to a standard SQL Query to retreive components with current many-to-many setup? Than you again! – Jason Nov 06 '13 at 22:42
  • I've added more code to my example to show you could add and display components using the intermediate object relationship. It is a more convoluted for sure, but does give you more flexibility as well as working around the CF limitation on ordering many-to-many. – CfSimplicity Nov 07 '13 at 17:21
  • Thank you CfSimplicity! That worked a treat. I actually thought it would get messy to implement, but in the end, not much more to it than just setting up a many-to-many. THanks! – Jason Nov 13 '13 at 04:50