Relationships
Copyright 2008-2012 ManyDesigns srl. All rights reserved.
Contents
Purpose
An important part of data modeling is to identify the relationships that exist between the classes. Relationships express the natural associations between units of information (e.g., between an objects and its parts), are the basis for navigation between objects, and enable database normalization.
All relationships in ManyDesigns Portofino are implemented using relationship attributes, conceptually very similar to foreign keys. Like for foreign keys, different combinations and configurations of relationship attributes can produce different types of relationship: one-to-many, one-to-one, and many-to-many.
One-to-many
One-to-many relationships are the most straightforward application of a relationship attribute. Let's suppose for example that we have two classes (Factory and Product) and a one-to-many relationship between them ("factory makes products").
To create the relationship:
- visit the class on the "many" side of the relationship, in this case Product;
- add a relationship attribute to it;
- choose a name for the attribute, e.g. "factory_product";
- think how a product sees its factory: the factory is the "producer" of the product. Use that ("producer") as the pretty name;
- choose the class on the "one" side of the relationship (Factory) as the opposite end class;
- think how a factory sees its products: this can simply be "products"; use that as the opposite end name;
- make sure you leave the field one to one unchecked;
- fill in the rest of the fields according to your needs.
It is important that you familiarize with the way pretty name and opposite end name affect the user interface at the downstairs level. A common mistake is to invert them. With a little practice, the correct use of the relationship attribute becomes natural.
In Portofino there is no difference between one-to-many and many-to-one relationships from a functional and implementation point of view. What matters is what classes you choose for the "one" and "many" ends of the relationship.
Zero-or-one cardinality
Depending on the value you choose for the field required, you can have two situations:
- required is checked: the relationship is one-to-many;
- required is unchecked: the relationship is zero-or-one-to-many;
A zero-or-one-to-many means that the object on the "many" side of the relationship can exist even if not related to any object on the "one" side. In the factory/product example, this would mean that a product can exists even if no factory produces it.
One-to-one
A one-to-one is very similar to a one-to-many. In fact, the field one to one on the relationship attribute is what makes the difference between the two: if unchecked, you get a one-to-many; if checked, you get a one-to-one.
A frequent question is: since one-to-one's seem symmetrical, where should the relationship attribute be put?
Let's see an example. We have two classes (Partner and Partner_agreement), related through a one-to-one relationship. Think: in temporal terms, which comes first, the partner or the partner agreement? In our view, the partner comes first and is somehow a longer-lived object than the partner agreement. We try to move away from it any information that is secondary or transient. The conclusion is that the relationship attribute should belong to the Partner_agreement class.
Behind the scenes, Portofino enforces one-to-one relationships through a UNIQUE constraint in the database.
Many-to-many
If you are familiar with database design, you know that a many-to-many relationship is implemented, at a physical level, through an intersection table. This table's purpose is to hold the two foreign keys which reference the tables at the two ends of the relationship.
In Portofino things work similarly. Let's suppose that we have two classes (User and Server) and that we want a many-to-many relationship between them, representing the idea that a user can access many servers and a server has many authorized users.
The steps to create the relationship are:
- set up the intersection class
- set up the first end of the relationship
- set up the second end of the relationship
These are discussed in greater detail here:
Create the intersection class just like any other class, but notice the following:
- Choose a name that fits the purpose of the relationship: e.g., user_server in the example.
- The pretty name and pretty plural are not particularly important as we'll make sure they are not used in the user interface. You can use the same value as for name.
- Check the relationship flag. This is very important as it marks the class as an intersection class and affects the way Portofino presents it to users.
- In almost all cases you will want to leave tab unchecked.
Add a relationship attribute on the intersection class:
- Use the name of the class on the first end (User) as the name and pretty name of the attribute: e.g., user in the example.
- Select the class on the first end (User) as the opposite end class.
- Think about how a user sees the servers connected through the relationship, e.g.: "authorized servers". Use this as the opposite end name.
- Check the immutable flag. This a recommendation, not a rule, but it generally works well with many-to-many relationships.
Setting up the second end
Follow the same instructions used for the first end but this time refer to the class on the second end (Server in the example).
Once you have set up the relationship you can go downstairs and test it. Visit a user's details page. In the relationships section, notice the link "connect to server" instead of "add user_server".
N-ary relationships
The intersection class can have as many relationship attributes as you want. This effectively implements an n-ary relationship (a many-to-many-to-many-... relationship).
Attributes on the intersection class
The intersection class, just like a regular class, can have as many attributes as you need, other than the two relationship attributes.
In the User/Server example, if you want to track the start and end dates when a user is authorized to access a server, you can add two date attributes (start date and end date) to the user_server class.
Contexts
A context is a navigation path that links objects through relationships. Contexts are created checking the "context" attribute in one-to-many relationships. (e.g. USA>California>Sacramento trough the relationships "State of" and "Capital of").
Context allows easier search of objects through cascading selections. A cascading selection is the sequence of selections, where each options list is depending on the previous item selected.
Contexts are used in:
- to create breadcrumbs in Read pages (read more on breadcrumbs in this article by Jakob Nielsen)
- for cascading select in Create, Update pages to select opposite object in the relationship.
- for cascading select in Search page to set a filter based on the context.
Autoconnect to user
If a relationship attribute has the class User as the opposite end class, you can set the autoconnect to userflag. In this way, the value of the attribute is automatically set to the id of the currently logged in user.
A pre-requisite to this function is that user management has been configured.
Recursive relationships
A one-to-many or a one-to-one relationship is "recursive" if it relates a class to itself. More formally, a relationship attribute is recursive if the class that owns the class is the same as the opposite end class or asuperclass of it.
A recursive one-to-many relationship allows you to model "trees" of objects.
A recursive one-to-one relationship allows you to model "sequences" of objects.
[The following discussion applies only to version 3.0.x or earlier. Since Portofino 3.1.x, the _left/_right/_depth columns and the _up/_down views are no longer used. See the ManyDesigns Portofino 3.1 release notes for an explanation.]
When you create a recursive relationship attribute, Portofino also creates:
- three columns with suffixes "_left", "_right" and "_depth";
- two views with suffixes "_up" and "_down".
These extra columns and views allow you to query the tree/sequence of objects in a very efficient way using SQL, even on databases that do not support recursive queries. You may find this useful when you deal with reports.
For more information on this technique, see Joe Celko's excellent book "Trees and Hierarchies in SQL for Smarties" published by Morgan Kaufmann.
Previous: Attributes
Next: User management