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" ]
}
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)