Add a 1:1 relationship
Prerequisite: You must be familiar with the
Syntax used in Tutorials and
have already created an
extension.
We also assume that you are familiar with dashboard design within
iTop and OQL.
You want to create a field which would be a one to one relationship, but such type of field does not exist, so how could you do.
-
This solution is just one of the possible options.
-
This solution has some limitation: the relationship will be editable on one side only
-
The relation must be visible from both side of the relationship.
-
In this solution, I have considered that it was not possible to create a relationship with someone already engaged, no forced divorce ๐๐คช!, but this is an option, you could allow it
1:1 between 2 classes
Declare 2 ExternalKeys
Let's imagine 2 classes: Person and CompanyCar. A Person may have a company car, but only one. A company car can be allocated to one Person maximum.
- itop-design / classes / class@Person / fields
-
<field id="companycar_id" xsi:type="AttributeExternalKey" _delta="define"> <filter><![CDATA[SELECT CompanyCar AS cc WHERE cc.person_id = 0]]></filter> <dependencies/> <sql>companycar_id</sql> <target_class>CompanyCar</target_class> <is_null_allowed>true</is_null_allowed> <on_target_delete>DEL_MANUAL</on_target_delete> <allow_target_creation>true</allow_target_creation> </field>
- itop-design / classes / class@CompanyCar/ fields
-
<field id="person_id" xsi:type="AttributeExternalKey" _delta="define"> <filter/> <dependencies/> <sql>person_id</sql> <target_class>Person</target_class> <is_null_allowed>true</is_null_allowed> <on_target_delete>DEL_MANUAL</on_target_delete> <allow_target_creation>false</allow_target_creation> </field>
Force Keys synchronization
Replace in the code below the constants by your own classes names and field code and that's all
- Person
-
class SynchroOneToOneRelation implements iApplicationObjectExtension { const CURRENT_CLASS = 'Person'; const KEY_TO_CURRENT = 'person_id'; const REMOTE_CLASS = 'CompanyCar'; const KEY_TO_REMOTE = 'companycar_id'; public function OnDBInsert($oObject, $oChange = null) { if ((get_class($oObject) == static::CURRENT_CLASS ) && ($oObject->Get(static::KEY_TO_REMOTE) > 0)) { $oNewRemote = MetaModel::GetObject(static::REMOTE_CLASS, $oObject->Get(static::KEY_TO_REMOTE), false, true); if ($oNewRemote) { $oNewRemote->Set(static::KEY_TO_CURRENT, $oObject->GetKey()); $oNewRemote->DBUpdate(); } } } public function OnDBUpdate($oObject, $oChange = null) { if (get_class($oObject) == static::CURRENT_CLASS ) { $aChanges = $oObject->ListPreviousValuesForUpdatedAttributes(); if (array_key_exists(static::KEY_TO_REMOTE, $aChanges)) { if ($oObject->Get(static::KEY_TO_REMOTE) > 0) { $oNewRemote = MetaModel::GetObject(static::REMOTE_CLASS, $oObject->Get(static::KEY_TO_REMOTE), false, true); if ($oNewRemote) { $oNewRemote->Set(static::KEY_TO_CURRENT, $oObject->GetKey()); $oNewRemote->DBUpdate(); } } if ($aChanges[static::KEY_TO_REMOTE] > 0) { $oOldRemote = MetaModel::GetObject(static::REMOTE_CLASS, $aChanges[static::KEY_TO_REMOTE], false, true); if ($oOldRemote) { $oOldRemote->Set(static::KEY_TO_CURRENT, 0); $oOldRemote->DBUpdate(); } } } } } public function OnDBDelete($oObject, $oChange = null) { if (get_class($oObject) == static::CURRENT_CLASS && ($oObject->Get(static::KEY_TO_REMOTE) > 0)) { $oOldRemote = MetaModel::GetObject(static::REMOTE_CLASS, $oObject->Get(static::KEY_TO_REMOTE), false, true); if ($oOldRemote) { $oOldRemote->Set(static::KEY_TO_CURRENT, 0); $oOldRemote->DBUpdate(); } } } public function OnCheckToWrite($oObject) { return; } public function OnIsModified($oObject) { return false; } public function OnCheckToDelete($oObject) { return; } }
Prevent edition on one side
In order to avoid an infinite loop of update, it's easier to lock the edition on one side of the relationship
- CompanyCar
-
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '') { if ($sAttCode == 'person_id') { return(OPT_ATT_READONLY | parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState)); } return parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState); } public function GetInitialStateAttributeFlags($sAttCode, &$aReasons = array()) { if (($sAttCode == 'person_id')) { return(OPT_ATT_HIDDEN | parent::GetInitialStateAttributeFlags($sAttCode, $aReasons)); } return parent::GetInitialStateAttributeFlags($sAttCode, $aReasons); }
Allowing stealing
Let's suppose we want to reaffect a car to a driver, even if the car is already allocated:
-
Change the filter to allow stealing a company car to its current driver,
-
But when you update the previous driver, to empty his companycar_id, you must not enter in an infinite loop.
๐ง Find and describe a mechanism to avoid looping: for example
storing on the OldDriver object an on the flight property like
_no_propagation_on_companycar_id
and test it in
OnDBUpdate to stop the loop
1:1 within the same class
Let's suppose that we have a Person field called In couple
with
pointing to another person, but himself.
๐ง requires a bit of thinking to avoid infinite loop as we must allow edition on both ends as there is just one.