Lesson 06: Relationships

As a System Admin I want to invite, view and edit Shop Owners and link them to a shop.



Lesson Outcomes

By the end of this lesson you should:

  • Be able to declare relationships between objects
  • Understand multiplicity and support for different relationships in Helium
  • Be able to set and reference relationships
  • Make use of the relationshipIn selector



New & Modified App Files

./model/Shop.mez

./model/ShopOwner.mez

./web-app/lang/en.lang

./web-app/presenters/ShopMgmt.mez

./web-app/presenters/ShopOwnerUserMgmt.mez

./web-app/views/ShopDetails.vxml

./web-app/views/ShopMgmt.vxml

./web-app/views/ShopOwnerDetails.vxml

./web-app/views/ShopOwnerUserMgmt.vxml



Data Model Additions

In order for us to demonstrate relationships between objects, we will add an additional user role, namely, a "Shop Owner". In addition to the object and role we also add CRUD functionality for the ShopOwner object. Seeing as this type of functionality has already been demonstrated in the tutorial, we will not discuss it again here. The source code for this lesson can be used a reference for this.



Multiplicity

Multiplicity refers to how few and how many of one entity can be connected to another entity where the entities in this case are shop and shop owners. Helium supports the following:

  • One to One
  • Many to One
  • One to Many
  • Many to Many



One to One Relationships

For a one to one relationship each shop has only one owner and an owner only has one shop. To demonstrate this we need to declare the relationship on either the Shop or ShopOwner model object. In this example we will add the relationship to the Shop object. 

persistent object Shop {
	
	.
	.
	.
	
	// Related shop owner
	@OneToOne
	ShopOwner owner via shop;
}

As can be seen above, the relationship is declared using the @OneToOne annotation. This is followed by the type of the related object and the names that will be used to reference the relationship. In this case owner will be used for reference from the Shop object and shop will be used as reference from the ShopOwner object. See the example functions below as a demonstration of this. 

ShopOwner getShopOwner(Shop shop) {
	return shop.owner;
}
Shop getShop(ShopOwner shopOwner) {
	return shopOwner.shop;
}

In addition to the declaration of the relationship, we will also make changes to the ShopMgmt view and ShopMgmt unit to set and display the relationship. For the view we need to add a select box:

<select label="select.shop_owner">
	<binding variable="shop">
		<attribute name="owner"/>
	</binding>
	<collectionSource function="getShopOwners">
		<displayAttribute name="firstName"/>
		<displayAttribute name="lastName"/>
	</collectionSource>
</select>

Note the similarity between the select box used above and the select box covered in Lesson 5. Both binds the selected value to a variable but whereas the previous example specified a custom enum as the source for the dropdown, we now specify a collection. With this we then also need to specify the attributes for the collection objects that will be displayed. In this case we selected attributes representing the first and last name of the shop owner. Also take note that validation using validators on the model is not supported for relationships. For this reason manual validation needs to be done using an if statement and a popup message in the saveShop functions:

if(shop.owner == null) {
	Mez:alertError("alert.shop_owner_required");
	return null;
}


We now add a column to the shop table:

<column heading="column_heading.shop_owner">
	<attributeName>owner.firstName</attributeName>
	<attributeName>owner.lastName</attributeName>
</column>

Note that we can reference attributes of the related ShopOwner object by first referencing the relationship and then the attributes in the related object.

The final addition that we will make to demonstrate the one to one relationship is info widgets on the ShopOwnerDetail view:

<info label="info.shop_owner_firstname">
	<binding variable="shop">
		<attribute name="owner.firstName"/>
	</binding>
</info>
<info label="info.shop_owner_lastname">
	<binding variable="shop">
		<attribute name="owner.lastName"/>
	</binding>
</info>
		
<info label="info.shop_owner_mobile_number">
	<binding variable="shop">
		<attribute name="owner.mobileNumber"/>
	</binding>
</info>
		
<info label="info.shop_owner_email_address">
	<binding variable="shop">
		<attribute name="owner.emailAddress"/>
	</binding>
</info>
 

The screenshots below further illustrates the additions up to this point.





































Manual validation using an if statement and alert is required as model validators are not supported for relationships.














One to Many Relationships

For a many to one relationship each shop can have multiple owners. We will keep the relationship on the Shop object. We will, however, change the multiplicity and rename the relationship accordingly.

Seeing as each shop can now have more than one owner, a select box alone is not enough. In addition to a select box, we will also need a button to confirm the linking of a shop owner with a shop and a table to display the selected owners of a shop. We will include a similar table on the ShopDetails view.

First we need to make the model changes:

 persistent object Shop {
	.
	.
	.
	@OneToMany
	ShopOwner owners via shop;
}

If we were to place the relationship on the ShopOwner object at this point, we would have used the @ManyToOne annotation instead.

We can then replace the single select box on the ShopMgmt view with a select box, submit button and table as follows:

 <select label="select.shop_owner">
	<binding variable="ownerToAdd"/>
	<collectionSource function="getAllShopOwners">
		<displayAttribute name="firstName"/>
		<displayAttribute name="lastName"/>
	</collectionSource>
</select>
		
<submit label="submit.add_shop_owner" action="addShopOwner"/>
		
<table title="table_title.shop_owners">
	<collectionSource function="getCurrentShopOwners"/>
	<column heading="column_heading.name">
		<attributeName>firstName</attributeName>
		<attributeName>lastName</attributeName>
	</column>
	<rowAction label="button.remove" action="removeShopOwner">
		<binding variable="ownerToRemove"/>
	</rowAction>
</table>

The backing functions for these view components are:

// Return all shop owners in the system
ShopOwner[] getAllShopOwners() {
	return ShopOwner:equals(deleted, false);
}
// Return the shop owners that are linked to the current shop
ShopOwner[] getCurrentShopOwners() {
	return shop.owners;
}
string addShopOwner() {
	shop.owners.append(ownerToAdd);
	ownerToAdd = null;
	return null;
}
string removeShopOwner() {
	ShopOwner[] shopOwners = getAllShopOwners();
	for(int i = 0; i < shopOwners.length(); i++) {
		ShopOwner currentShopOwner = shopOwners.get(i);
		if(ownerToRemove._id == currentShopOwner._id) {
			shop.owners.remove(i);
		}
	}
	return null;
} 

We can alternatively use the relationshipIn selector in the getCurrentShopOwners function to check if a relationship is set to a specific value or collection of values. In the example below the first argument is the name of the relationship and the second either an object instance or collection of object instances :

ShopOwner[] getCurrentShopOwners() {
	return ShopOwner:relationshipIn(shop, shop);
}


We can now also replace the info widgets on the ShopDetails view with a similar table to display the details of the owners of the shop:

 <table title="table_title.shop_owners">
	<collectionSource function="getCurrentShopOwners"/>
	<column heading="column_heading.first_name">
		<attributeName>firstName</attributeName>
	</column>
	<column heading="column_heading.last_name">
		<attributeName>lastName</attributeName>
	</column>
	<column heading="column_heading.mobile_number">
		<attributeName>mobileNumber</attributeName>
	</column>
	<column heading="column_heading.email_address">
		<attributeName>emailAddress</attributeName>
	</column>
</table>

For completeness' sake we also add a info widget on the ShopOwnerDetails view to display the name of the shop of the selected owner:

 <info label="info.shop">
	<binding variable="shopOwner">
		<attribute name="shop.name"/>
	</binding>
</info>

The screenshots below further illustrates the additions up to this point.








































On the "One" side of a relationship, it can be set using a normal assignment operator or binding from select box widget and cleared by setting it to null. On the "Many" side of a relationship it can be treated as a collection where append can be used to set the relationship and remove to clear it.











Many to Many Relationships

For a many to many relationship each shop can have multiple owners but a shop owner can also be a partial owner of more than one shop. We will once again change the multiplicity of the relationship in our model. We also update the via reference for our relationship from shop to shops to reflect this change:

persistent object Shop {
	.
	.
	.
	@ManyToMany
	ShopOwner owners via shops;
}

In this case, however, the only view changes we need to make are to the ShopOwnerDetails view where we now need to replace the info widget showing shop details to a table seeing as a shop owner can now also be an owner of multiple shops.

 <table title="table_title.shop_owner_shops">
	<collectionSource function="getShopOwnerShops"/>
	<column heading="column_heading.name">
		<attributeName>name</attributeName>
	</column>
	<column heading="column_heading.description">
		<attributeName>description</attributeName>
	</column>
</table>
Shop[] getShopOwnerShops() {
	return shopOwner.shops;
}




Self Referencing Relationships

In this lesson, we used two different objects and linked them using relationships. Helium, however, also support relationships between the same object.



Lesson Source Code

Lesson 6.zip