CouchDB is a document store. It does not materialize relations between documents. We do, however, have a standardized way of describing relations between cozy documents. This allows Cozy-Client to give us some automation.
Relations between documents are materialized under a relationships
object at the root of the document. Relations are referenced by their names, e.g. authors
.
Each relation is an object with a data
property, containing either one reference, or an array of references.
A reference is an object containing at least:
_type
: the name of the referenced doctype, e.g.io.cozy.contacts
_id
: the id of the referenced document.
For instance, a book -> authors relationship might be represented like this:
{
"_id": "mobydick",
"relationships": {
"authors": {
"data": [{ "_id": "hermanmelville", "_type": "io.cozy.contacts" }]
}
}
}
Please see the cozy-doctypes documentation for more insights about the syntax.
Cozy-client knows how to handle these relations thanks to the schema you provide at initialization:
const schema = {
books: {
doctype: 'io.cozy.books',
attributes: {},
relationships: {
authors: {
doctype: 'io.cozy.contacts',
type: 'has-many'
}
}
}
}
const client = new CozyClient({
uri: 'http://cozy.tools:8080',
token: '...',
schema
})
We explain below what are the different types of relations.
Cozy-Client predefines two basic relation types that must be specified in the schema:
'has-one'
: the relation has a unique reference.'has-many'
: the relation can have several references.
The files implements a special type of relation: 'io.cozy.files:has-many'
. This relation name is referenced_by
, which is an array of references, just like the 'has-many'
relation.
The stack implements routes to handle this kind of relation on the /files
endpoint. See the stack documentation.
Note specific view index are defined on referenced_by
relationships, allowing fast queries on it.
You are free to create your own relation type if you need a special behaviour.
For instance, Banks doctypes implements HasManyBills
or HasManyReimbursements
.
Note there are two others basic relations, that are here for backward compatibility:
'has-one-in-place'
'has-many-in-place'
'has-one'
or 'has-many'
.
With these relations, instead of using the relationships
attribute, you reference directly the ids of linked documents at the root of your data:
const schema = {
books: {
doctype: 'io.cozy.books',
attributes: {},
relationships: {
authors: {
doctype: 'io.cozy.contacts',
type: 'has-many-in-place'
}
}
}
}
const book = {
"_id": "mobydick",
"authors": [ "hermanmelville" ]
}
It is possible to assign metadatas to relationships. You need to modify the content of the document relationships somehow and save the modified document.
const doc = {
_id: "mobydick",
relationships: {
authors: {
data: [{
_id: "hermanmelville",
_type: "io.cozy.contacts"
}]
}
}
}
doc.relationships.authors.data[0].metadata = { addressId: "123" }
await client.save(doc)
Relations are not loaded eagerly by default. If you want your query to load your relations for you, you will have to name them in an include()
request.
const query = Q('io.cozy.books')
.include(['authors'])
.limitBy(20)
You will then find your relations under the data
attribute:
const response = await client.query(query)
const docs = response.data
const firstDoc = docs[0]
const firstAuthors = firstDoc.authors.data
When the relationship is a has-many
type, you can call the add(docs)
to create the relationship:
const otherAuthors = [{_id: 'Rivest'}, {_id: 'Shamir'}]
const response = await client.query(query)
const docs = response.data
const firstDoc = docs[0]
firstDoc.authors.add(otherAuthors)
add
also accepts single document:
firstDoc.authors.add({_id: 'Adleman'})
Likewise, when the relationship is a has-one
, use add(doc)
:
const printer = {
_id: 'abc123',
_type: 'io.cozy.company',
name: 'Harper & Brothers'
}
const response = await client.query(query)
const docs = response.data
const firstDoc = docs[0]
firstDoc.printingCompany.add(printer)
For has-many
relationships, use the remove(docs)
method:
const wrongAuthors = [{_id: 'James Wrong' }, {_id: 'Henry Mistake'}]
const response = await client.query(query)
const docs = response.data
const firstDoc = docs[0]
firstDoc.authors.remove(wrongAuthors)
Just like add
, remove
accepts a single document.
For has-one
relationships, just use remove()
, with no argument:
const response = await client.query(query)
const docs = response.data
const firstDoc = docs[0]
firstDoc.printingCompany.remove()
Simply pass the reference to your file as a third parameter to create()
:
const photo = { _id: "sunset.jpg" }
const reference = { _id: "1324", _type: "io.cozy.photos.albums" }
const albumReference = { albums: [ albumReference ] }
await client.create('io.cozy.files', photo, albumReference)