Multiple has_many's of the same class

Multiple has_many relations of the same class

It doesn't really matter which side of a one-to-many relationship you're looking at: these relations always come in pairs: a has_one in one class maps to a has_many in the other class. When you need to use more then one such relation involving the same class, you just need to make sure each has_many knows which has_one it belongs to. So just the classname won't do - you need the extra bit of dot-notation to make this work. Read more about using dot-notation here.

Two ComplexTableFields...

In the following example we have a Page with two has_many relations to the same 'Person' class, called 'Boys' and 'Girls'. We'll use twoseparate ComplexTableFields on the page to manage these two relations. First we'll create the relation-couples by using dot-notation and then adding the CTFs shouldn't be much of a problem:

class Person extends DataObject {

	static $db = array(
		'Name' => 'Varchar(120)
	);

	static $has_one = array(
		'BoysPage' => 'MyPage',
		'GirlsPage' => 'MyPage'
	);
}
class MyPage extends Page{

	static $has_many = array(
		'Boys' => 'MyPage.BoysPage',
		'Girls' => 'MyPage.GirlsPage',
	);

	function getCMSFields() {

		$fields = parent::getCMSFields();

		fields->addFieldsToTab("Root.Content.Boys", 
			array($boys = new ComplexTableField($this, 'Boys', 'MyPage'))
		);
		$fields->addFieldsToTab("Root.Content.Girls", 
			array($girls = new ComplexTableField($this, 'Girls', 'MyPage'))
		);

		return $fields;
	}
}

There is a problem...
Unfortunately this is not going to work. Although the relations are firmly mapped and two fields 'BoysPageID' and 'GirlsPageID' are created in the Person DataTable, SilverStripe will still only write the Page's ID to the GirlsPageID field - no matter what CTF you use. This is because SilverStripe - or the CTF - doesn't seem to acknowledge dot-notation on witing!! But we can overcome this by telling each CTF which parent ID to use:

function getCMSFields() {

	$fields = parent::getCMSFields();

	fields->addFieldsToTab("Root.Content.Boys", 
		array($boys = new ComplexTableField($this, 'Boys', 'MyPage'))
	);
	$fields->addFieldsToTab("Root.Content.Girls", 
		array($girls = new ComplexTableField($this, 'Girls', 'MyPage'))
	);

	// Tell each CTF which parent ID they should use
	$boys->setParentIdName('BoysPageID');
	$girls->setParentIdName('GirlsPageID');

	return $fields;
}

Not quite there yet...
This should now work - but it still doesn't quite. Although for boys, the page ID is now correctly written to the BoysPageID field, it is still also added to the GirlsPageID. For some reason each Person written is always added to the last page ID field as well, regardless of the ParentID setings. Now you can fight this, or just let it be, by creating an extra'dummy' relation to catch this behaviour:

In Person:

static $has_one = array(
	'BoysPage' => 'MyPage',
	'GirlsPage' => 'MyPage',
	'MyPage' => 'MyPage'     // dummy
);

In MyPage:

static $has_many = array(
	'Boys' => 'MyPage.BoysPage',
	'Girls' => 'MyPage.GirlsPage',
	'Persons' => 'MyPage'           // dummy
);

That will take care of that. Now in your Page templates you can even use all three relations to either get boys, girls or all persons belonging to that page:

<h2>Boys</h2>
<% control Boys %>$Name <% end_control %> 

<h2>Girls</h2>
<% control Girls %>$Name <% end_control %> 

<h2>Persons</h2>
<% control Persons%>$Name <% end_control %> 

Clean up...

If scaffolding is used for the popups in the Boys and Girls ComplexTableField, we'll find some (un)expected dropdowns: 'Girls Page' on the Boys popup, 'Boys Page' on the girls popup, and 'Persons' on both. To get rid of them, we need a getCMSFields() method in the Person object, that will change these popups into hidden fields.

In the Person class:

	function getCMSFields() {

		$fields = parent::getCMSFields();

		$fields->replaceField('BoysPageID', new HiddenField('BoysPageID'));
		$fields->replaceField('GirlsPageID', new HiddenField('GirlsPageID'));
		$fields->replaceField('MyPageID', new HiddenField('MyPageID'));

		return $fields;
 	}
 

Note: in an earlier post, since I hadn't stunbled upon the dot-notation solution, I tackled these one-to-many relations by just subclassing - in this case - the Person class into a Boy and a Girl class. And although I still think that this is sometimes the better solution, it isn't alway practical in the managment division... 

Comments

Het versturen van reacties is uitgeschakeld.

RSS feed voor reacties op deze pagina