Relationships

Introduction

In MongoDB, documents can have relationships with other documents (see MongoDB Relationships documentation section). 

RESTHeart allows to declare the existing relationships in a collection, so that it automatically adds the links to related documents in its representation.

As discussed in the Representation Format section, RESTHeart uses the HAL+json hypermedia format. HAL builds up on 2 simple concepts: Resources and Links

  • Resources have state (plain JSON), embedded resources and links
  • Links have target (href URI) and relations (aka rel)

Let’s see the following simple example, a GET on a document resource:

$ http -a a:a GET 127.0.0.1:8080/test/coll/doc
HTTP/1.1 200 OK
...
{
    "_etag": {
        "$oid": "55e84f5ac2e66d1e0a8e46b8"
    }, 
    "_id": "doc", 
    "descr": "a document for testing but modified"
}

Declaring a relationship will add to the _links property the related documents links.

The rels collection metadata

In RESTHeart, not only documents but also dbs and collections have properties. Some properties are metadata, i.e. they have a special meaning for RESTheart that influences its behavior.

The collection metadata property rels allows to declare the existing relationship so that the representation of the collection’s documents auto-magically include the links to the referenced documents between the HAL _link property

rels is an array of rel objects having the following format:

{
  "real": "<relid>",
  "type": "<type>",
  "role": "<role>",
  "target-db": "<dname>",
  "target-cill": "<collname>",
  "ref-field": "<reffieldname>"
}
Property
Description
Mandatory
rel

Name of the relationship.

Yes
type Type of relationship: ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY or MANY_TO_MANY Yes
role Role of the relationship: OWNING or INVERSE. This says where the reference field is. OWNING means it is in this document, INVERSE means that it is on the target document. Yes
target-db Database name of the referenced document(s)

No. If omitted the target db is supposed to be the current one

target-coll Collection's name of the referenced document(s) Yes
ref-field

Name of the first level field storing the id(s) of the referenced document(s) or the json path expression that resolves to the ids of the referenced documents (to reference nested fields).

The value of the first level field must be either the id string (in case the multiplicity of other side of the relationship is ONE) or an array of id strings (in case the multiplicity of other side of the relationship is MANY)

Note: json path expression must start with $, example:

$.a.nested.property will bind to {'a':{'nested':{'property':'docid'}}} and resolve to the value 'docid'

Yes

Examples

Tree of documents

In this example, we create a collection to store documents organized in a tree (each document has a single parent document).

Let’s create a collection, declaring a many-to-one relationship called parent, so that documents can refer a parent document in the collection itself.

$ http -a a:a PUT 127.0.0.1:8080/test/parentcoll rels:='[{"rel":"parent","type":"MANY_TO_ONE","role":"OWNING","target-coll":"parentcoll","ref-field":"parent"}]'
HTTP/1.1 201 CREATED
...

Let’s now create few documents, specifying the parent property:

$ http -a a:a PUT 127.0.0.1:8080/test/parentcoll/root parent=root
HTTP/1.1 201 CREATED
...
 
$ http -a a:a PUT 127.0.0.1:8080/test/parentcoll/1 parent=root
HTTP/1.1 201 CREATED
...
 
$ http -a a:a PUT 127.0.0.1:8080/test/parentcoll/1.1 parent=1
HTTP/1.1 201 CREATED
...
 
$ http -a a:a PUT 127.0.0.1:8080/test/parentcoll/1.2 parent=1
HTTP/1.1 201 CREATED
...

If we now get the document /test/parentcoll/1.2, the _links property includes parent with the correct URI of the document /test/parentcoll/1

$ http -a a:a GET 127.0.0.1:8080/test/parentcoll/1.2
HTTP/1.1 200 OK
...
{
    "_etag": {
        "$oid": "55f15b43c2e65448b566d18b"
    }, 
    "_id": "1.2", 
    "_lastupdated_on": "2015-09-10T10:28:19Z", 
    "_links": {
        "curies": [], 
        "self": {
            "href": "/test/parentcoll/1.2"
        }
    }, 
    "parent": "1"
}

One-to-Many, owning

In this example, we create two collections: bands and albums; of course, each band has a 1:N relationship to albums.

$ http -a a:a PUT 127.0.0.1:8080/test/bands rels:='[{"rel":"albums","type":"ONE_TO_MANY","role":"OWNING","target-coll":"albums","ref-field":"albums"}]' descr="music bands"
HTTP/1.1 201 CREATED
...
 
$ http -a a:a PUT 127.0.0.1:8080/test/albums descr="albums published by music bands"
HTTP/1.1 201 CREATED
...

Let’s now create few albums:

$ http -a a:a PUT 127.0.0.1:8080/test/albums/Disintegration year:=1989
HTTP/1.1 201 CREATED
...
 
$ http -a a:a PUT 127.0.0.1:8080/test/albums/Wish year:=1992
HTTP/1.1 201 CREATED
...
 
$ http -a a:a PUT 127.0.0.1:8080/test/albums/Bloodflowers year:=2000
HTTP/1.1 201 CREATED
...

Now we create the band referring these albums:

$ http -a a:a PUT "127.0.0.1:8080/test/bands/The Cure" albums:='["Disintegration","Wish","Bloodflowers"]'
HTTP/1.1 201 CREATED
...

If we now get The Cure document, we can notice the albums link:

/test/albums?filter={'_id':{'$in':['Disintegration','Wish','Bloodflowers']}}"

Since the other side of the relationship has cardinality N, the albums link is a collection resource URI with a filter query parameter.

$ http -a a:a GET "127.0.0.1:8080/test/bands/The Cure"
HTTP/1.1 200 OK
...
    "_embedded": {}, 
    "_etag": {
        "$oid": "55f16029c2e65448b566d191"
    }, 
    "_id": "The Cure", 
    "_lastupdated_on": "2015-09-10T10:49:13Z", 
    "_links": {
        "albums": {
            "href": "/test/albums?filter={'_id':{'$in':['Disintegration','Wish','Bloodflowers']}}"
        }, 
        "curies": [], 
        "self": {
            "href": "/test/bands/The Cure"
        }
    }, 
    "albums": [
        "Disintegration", 
        "Three Imaginary Boys", 
        "Seventeen Seconds"
    ]
}

If we want to get the referenced document with httpie (or curl) we need to issue the following command:

http -a a:a GET 127.0.0.1:8080/test/albums?filter="{'_id':{'\$in':['Disintegration','Wish','Bloodflowers']}}"

Note the “\” char prefixing the operator $in. This prevents the command line interpreter replacing $in with an (not existing) environment variable.

One-to-Many, inverse

We’ll resemble the previous example, but using an inverse relationship, i.e. the filed storing the relationship will be stored in the album documents.

$ http -a a:a PUT 127.0.0.1:8080/test/bandsi rels:='[{"rel":"albums","type":"ONE_TO_MANY","role":"INVERSE","target-coll":"albums","ref-field":"band"}]' descr="music bands"
HTTP/1.1 201 CREATED
...
 
$ http -a a:a PUT 127.0.0.1:8080/test/albumsi descr="albums published by music bands"
HTTP/1.1 201 CREATED
...

Let’s now create few albums:

$ http -a a:a PUT 127.0.0.1:8080/test/albumsi/Disintegration year:=1989 band="The Cure"
HTTP/1.1 201 CREATED
...
 
$ http -a a:a PUT 127.0.0.1:8080/test/albumsi/Wish year:=1992 band="The Cure"
HTTP/1.1 201 CREATED
...
 
$ http -a a:a PUT 127.0.0.1:8080/test/albumsi/Bloodflowers year:=2000 band="The Cure"
HTTP/1.1 201 CREATED
...

Now we create the band referred by these albums:

$ http -a a:a PUT "127.0.0.1:8080/test/bandsi/The Cure" descr="The Cure are an English rock band formed in Crawley, West Sussex, in 1976"
HTTP/1.1 201 CREATED
...

If we now get “The Cure” document, we can notice the albums link:

/test/albumsi?filter={'band':'The Cure'}
$ http -a a:a GET "127.0.0.1:8080/test/bandsi/The Cure"
HTTP/1.1 200 OK
...
    {
    "_etag": {
        "$oid": "55f19409b8e449c1e1304ec5"
    }, 
    "_id": "The Cure", 
    "_links": {
        "albums": {
            "href": "/test/albums?filter={'band':'The Cure'}"
        }, 
        "curies": [], 
        "self": {
            "href": "/test/bandsi/The Cure"
        }
    }, 
    "descr": "The Cure are an English rock band formed in Crawley, West Sussex, in 1976"
}