Entity Relations

Modeling Entity Relationships

Relationships between entities can be declared in the application model file via the addition of a ‘relations’ block inside an entity’s declaration. Relationships are based on resource id’s by default, although it is possible to specify non-default key fields in the configuration, or implement complex joins directly by maintaining the entity’s controller and model. ‘relations’ blocks look as follows:

            "relations": [
                    {
                    "relName": "ToOwner",
                        "properties": {
                            "relType": "hasOne",
                            "toEntity": "Owner"
                        }
                    }
                ]

The sample relations block illustrates the declaration of a ‘hasOne’ relationship between ‘Car’ and ‘Owner’ making use of default-keys (ID).

hasOne Relationship

‘hasOne’ relationships establish a one-to-one relationship between two modeled entities. As an example, let’s posit that a car can have one owner. If the car and owner were modeled as entities, we could declare a ‘hasOne’ (1:1) relationship between them. The relation would be added to the ‘relations’ block inside the ‘Car’ entity definition (as shown above).

A break-down of the relations block fields is as follows:

{
    "relations": [
    // The 'entities' block contains an array of relations belonging to the containing
    // entity definition.  Each relation is defined from the perspective of the
    // containing entity having a relationship of the specified type (in this case
    // hasOne), with the entity referenced in the declaration.  A 'Car' has one 'Owner'
    // - in our example at least.
    {
        "relName": "Owner"
        // Field 'relName' refers to the name the relationship will be known by inside
        // the application and in the mux route end-point definition.  It must be
        // capitalized and written in CamelCase.  Any name may be chosen for this
        // field, but keep in mind the name will be exposed to the service consumer via
        // the URI, so something respecting the relationship enities and cardinality is
        // best.  For the example, we have chosen a relName of 'ToOwner' to demonstrate
        // the difference between the toEntity and relName fields.
        // 'relName' is a mandatory field in a relations declaration.

            "properties": {
            // The 'properties' block contains the details of the relationship.

            "relType":
            // Field 'relType' is used to indicate what sort of relationship is being
            // declared between the containing (from) entity and the toEntity.  
            // Valid values are {hasOne, hasMany and belongsTo}.
            // This is a mandatory field.

            "toEntity":
            // Field 'toEntity' is used to specify the target entity in the
            // relationship. The toEnity must be capitalized and provided in CamelCase
            // that matches that used in the toEntity's declaration.  The toEntity need
            // not appear prior to the containing entity in the model file or files.
            // This is a mandatory field.
            }
    }
    ]
}

A complete example of a model file containing ‘Car’ and ‘Owner’ entity definitions along with the ‘hasOne’ relationship is shown below. Notice that the ‘Car’ entity has been declared with an ‘OwnerID’ field. As our ‘Car’ model specifies that a ‘Car’ may have one ‘Owner’, it makes sense for the key-relationship to go from ‘Car’ back to the ‘Owner’.

By default jiffy looks for a targetEntity + ID field when attempting to generate the constraint on the database system. Although it is possible to override this behavior, it is recommended to use the jiffy standard. It is incumbent on the model designer to include the correct foreign-key fields in the model.

{
    "entities":  [
        {
            "typeName": "Owner",
            "properties": {
                "Name": {
                    "type": "string",
                    "format": "",
                    "required": false,
                    "unique": false,
                    "index": "nonUnique",
                    "selectable": "eq,like"
                },
                "RegistrationNumber": {
                    "type": "uint",
                    "format": "",
                    "required": false,
                    "unique": true,
                    "index": "",
                    "selectable": "eq,lt,gt"
                },
                "City": {
                    "type": "string",
                    "format": "",
                    "required": false,
                    "unique": false,
                    "index": "",
                    "selectable": "eq,lt,gt"
                }
            },
            "compositeIndexes": [
                {"index": "name, city"}
            ],
            "relations": [
                {
                "relName": "ToCar",
                    "properties": {
                        "relType": "hasOne",
                        "toEntity": "Car"
                    }
                }
            ],
            "ext_points": {
                "gen_controller": true,
                "gen_model": true
            }
        },
        {
            "typeName": "Car",
            "properties": {
                "Model": {
                    "type": "string",
                    "format": "",
                    "required": true,
                    "index": "nonUnique",
                    "selectable": "eq,like"
                },
                "Make": {
                    "type": "string",
                    "format": "",
                    "required": true,
                    "index": "nonUnique",
                    "selectable": "eq,like"
                },
                "OwnerID": {
                    "type": "uint64",
                    "format": "",
                    "required": false,
                    "unique": false,
                    "index": "nonUnique",
                    "selectable": "eq,lt,gt"
                }
            },
            "ext_points": {
                "gen_controller": true,
                "gen_model": true
            }
        }
    ]
}

The sample model file can be downloaded from the following location: hasOne.json.

hasMany Relationship

‘hasMany’ relationships establish a one-to-many relationship between two modeled entities. As an example, let’s posit that a library can have many books. If library and book were modeled as entities, we could declare a ‘hasMany’ (1:N) relationship between them. The relation would be added to the ‘relations’ block inside the ‘Library’ entity definition. Notice that the ‘Book’ entity has been declared with an ‘LibraryID’ field. As our ‘Library’ model specifies that a ‘Library’ may have many ‘Book’ entities, it makes sense for the key-relationship to go from ‘Book’ back to the ‘Library’.

By default jiffy looks for a targetEntity + ID field when attempting to generate the constraint on the database system. Although it is possible to override this behavior, it is recommended to use the jiffy standard. It is incumbent on the model designer to include the correct foreign-key fields in the model.

{
    "entities":  [
        {
            "typeName": "Library",
            "properties": {
                "Name": {
                    "type": "string",
                    "format": "",
                    "required": true,
                    "unique": false,
                    "index": "nonUnique",
                    "selectable": "eq,like"
                },
                "City": {
                    "type": "string",
                    "format": "",
                    "required": true,
                    "unique": false,
                    "index": "",
                    "selectable": "eq,lt,gt"
                }
            },
            "ext_points": {
                "gen_controller": true,
                 "gen_model": true
            },
            "compositeIndexes": [
                {"index": "name, city"}
            ],
            "relations": [
                    {
                    "relName": "Books",
                        "properties": {
                            "relType": "hasMany",
                            "toEntity": "Book"
                        }
                    }
                ]
    },
    {
        "typeName": "Book",
        "properties": {
            "Title": {
                "type": "string",
                "format": "",
                "required": true,
                "index": "nonUnique",
                "selectable": "eq,like"
            },
            "Hardcover": {
                "type": "bool",
                "format": "",
                "required": true,
                "index": "",
                "selectable": "eq,ne"
            },
            "Copies": {
                "type": "uint64",
                "format": "",
                "required": true,
                "index": "",
                "selectable": "eq,lt,gt"
            },
            "LibraryID": {
                "type": "uint64",
                "format": "",
                "required": true,
                "index": "nonUnique",
                "selectable": "eq,like"
            }
        },
        "ext_points": {
            "gen_controller": true,
            "gen_model": true
        }
    }
    ]
}

The sample model file can be downloaded from the following location: hasManyDefaultKeys.json.

Again, it seems that this model does not capture the entire story, as each book belongs to its respective library. This brings us to the ‘belongsTo’ relationship type.

belongsTo Relationship

‘belongsTo’ relationships are used to form the inverse of the ‘hasOne’ and ‘hasMany’ relations. Consider the library ‘hasMany’ books example; A library has many books, but we can also posit that a book belongs to a library; this is an example of a ‘belongsTo’ relationship. The JSON below extends the ‘Library’ -> ‘Book’ example by adding the ‘belongsTo’ relationship to the ‘Book’ entity definition:

{
    "entities":  [
        {
            "typeName": "Library",
            "properties": {
                "Name": {
                    "type": "string",
                    "format": "", 
                    "required": true,
                    "unique": false,
                    "index": "nonUnique",
                    "selectable": "eq,like"
                },
                "City": {
                    "type": "string",
                    "format": "", 
                    "required": true,
                    "unique": false,
                    "index": "",
                    "selectable": "eq,lt,gt,like"
                }
            },
            "compositeIndexes": [ 
                {"index": "name, city"}
            ],
            "relations": [
                    { 
                    "relName": "ToBooks",
                        "properties": {
                            "relType": "hasMany",
                            "toEntity": "Book"
                        }
                    }
                ],
                "ext_points": {
                    "gen_controller": true,
                    "gen_model": true
                }
    },
    {
        "typeName": "Book",
        "properties": {
            "Title": {
                "type": "string",
                "format": "", 
                "required": true,
                "default": "unknown title",
                "index": "nonUnique",
                "selectable": "eq,like"
            },
            "Author": {
                "type": "string",
                "format": "", 
                "required": false,
                "degault": "unknown author",
                "index": "nonUnique",
                "selectable": "eq,like"
            },
            "Hardcover": {
                "type": "bool",
                "format": "", 
                "required": true,
                "index": "",
                "selectable": "eq,ne"
            },
            "Copies": {
                "type": "uint64",
                "format": "", 
                "required": true,
                "index": "",
                "selectable": "eq,lt,gt"
            },
            "LibraryID": {
                "type": "uint64",
                "format": "", 
                "required": true,
                "index": "nonUnique",
                "selectable": "eq,like"
            }
        },
        "relations": [
                    { 
                    "relName": "ToLibrary",
                        "properties": {
                            "relType": "belongsTo",
                            "toEntity": "Library"
                        }
                    }
                ],
                "ext_points": {
                    "gen_controller": true,
                    "gen_model": true
                }
    }
    ]
}

The sample model file can be downloaded from the following location: hasManyBelongsTo.json.

By relying on the default key determinations for the ‘belongsTo’ relationship, the generator determines that the Book.LibraryID field should be matched against field Library.ID. If alternate keys are desired, they can be specified in the ‘refKey’ and ‘foreignKey’ property fields in the ‘belongsTo’ relation declaration.

What if more complex relationships are required?

At the moment the generator only supports ‘hasOne’, ‘hasMany’ and ‘belongsTo’ relations, as in practice these seem to be the most widely used. The generated code can be extended to accommodate additional relationships and joins if need be. There is a tentative plan to support more complex relations in the generator in the future. Most of the supporting code is in place, but the controller_rel templates would need to be enhanced to support it. In the meantime, a combination of foreign-keys and static filters or hand-coded SQL can be employed as an alternative to formally defined relationships in cases where the entity-id model is not sufficient. See the Custom Join Tutorial for an example of coding a custom-join between two entities.