3.0 proposal: remove the need for developers to think about idsField and relationshipsField when working with joins
See original GitHub issueI’m working on the technical design for editing relationships in Apostrophe 3.x.
The hardest part of this is the way joins are represented in the database, and how we make developers think about it.
idsField: 'productsIds'
, relationshipsField: 'productsRelationships'
and all that stuff. These things have magic defaults but under the hood it’s complicated.
All other schema field types in apostrophe stay in their lane. The data is stored in the property of the same name in the database. But joins scatter their ids to one field, their relationships to another, and the actual join gets loaded dynamically under the field’s name.
This is pretty good if you’re not working on the guts, because you only care about the actual join anyway. But if you’re working on the guts it’s pretty crazymaking.
To make matters worse, our REST interface doesn’t really understand joins when writing, so you have to patch the idsField
and relationshipsField
directly.
I think we should definitely fix our REST interface so that if you PUT, POST or PATCH a join field by its name, like _products
, and pass in an actual array of existing product pieces, it’s smart enough to say “oh they are updating the join, let’s just grab the _id and any _relationship property if present from each one. Cool.” It’s inefficient if they send the entire array fully populated, but it should work.
But beyond that, there’s a tougher call to make. Do we want to make the data model easy to understand internally, and make it easy to implement the backend UI? Or do we want to continue to make sacrifices there for slightly less frontend code?
I see two ways forward:
1. We continue to sweat idsField and relationshipsField, but we do all of that in the AposSchema
Vue component, where it is a special case in order to allow the interface to the individual field components, including AposInputJoin
, to stay simple and v-model based.
2. We reboot the whole approach to store joins under their own name. Want to join your salesperson with some products? Write this in your schema:
// Note no _ anymore
products: {
type: 'join',
withType: 'product,
// use the optional relationship feature. Not always needed
relationship: {
// units of this product sold by this particular salesperson
sold: {
type: 'integer'
}
}
}
Apostrophe stores this in your salesperson doc:
{
title: 'Willy Loman',
type: 'salesperson',
products: {
ids: [ 1, 2, 7 ],
relationships: {
'2': {
sold: 5
}
}
}
}
And when you find salespeople, or use our GET REST API, you get back this:
{
title: 'Willy Loman',
type: 'salesperson',
products: {
// _docs subproperty is populated on the fly at load time
_docs: [
{
title: 'Hair Tonic',
type: 'product',
_id: 2,
_relationship: {
sold: 5
}
},
...
],
// the data that powers the join is here too
ids: [ 1, 2, 7 ],
relationships: {
'2': {
sold: 5
}
}
}
}
What this does is give us a super clear model for how joins are stored. They are concretely stored under a property of the same name, just like other fields.
However, it makes frontend developers do something new. In a template, you would now write:
<h3>{{ data.salesperson.title }}: Assigned Products</h3>
{% for product of data.salesperson.products._docs %}
<h4><a href="{{ product._url }}">{{ product.title }} (sold: {{ product._relationship.sold }})</a></h4>
{% endfor %}
You see what’s different here: we have to go the extra step to ._docs
when we work with these joins in our frontend code.
This is this price we would pay for everything being stored in a sensible and intuitive way.
But, it’s a price. And, it’s a change for people to learn about.
What do you think? Input welcome!
Issue Analytics
- State:
- Created 3 years ago
- Comments:28 (24 by maintainers)
Top GitHub Comments
Cool. I think Bea and I agreed with you already in our hearts but needed a little push 😄
I move we put this puppy to bed.
The final proposal would be:
The field type is
relationship
, notjoin
.Relationship fields, if any, are named and configured like this:
You can also set the relationship fields by setting
._relationship
on one or more of the products in that array.Can I get a hell yeah?
+1 For renaming Join to Relationship.
From a technical marketing perspective, Relationship is standard vernacular. In addition, Relationship is interoperable across a wide array of stakeholders (Designers, PMs, Developers, Executives, etc) in its simplicity and basic recognition in English. As shown in our documentation, it’s difficult to describe Join without using the word relationship. As a rule of thumb, if we can simplify technical concepts without deeply violating their meaning, we should.