Skip to content

Commit

Permalink
wip: push leftHandSide relationship info for findHasMany/belongsTo
Browse files Browse the repository at this point in the history
always pushes the left hand side of a relationship, even when the
adapter does not return a payload with the left hand side's ids in
`relationships`.

What a mouthful. Let's unpack that.

Given the following model definitions:

```javascript
// app/models/person.js

export default DS.Model.extend({
  name: attr(),
  dogs: hasMany('dog', { async: true })
});
```

```javascript
// app/models/dog.js

export default DS.Model.extend({
  name: attr(),
  person: belongsTo('person', { async: true })
});
```

Given `PersonAdapter#findRecord` returns the following payload and is
pushed into the store:

```javascript
const payload = {
  data: {
    type: 'person',
    id: '1',
    attributes: {
      name: 'John Churchill'
    },
    relationships: {
      dogs: {
        links: {
          related: 'http://exmaple.com/person/1/dogs'
        }
      }
    }
  }
};
```

and the result of `DogAdapter#findHasMany` resolves with the following
payload:

```javascript
{
  data: [
    { id: 1, type: 'dog', attributes: { name: 'Scooby' } },
    { id: 2, type: 'dog', attributes: { name: 'Scrappy' } }
  ]
}
```

Notice how the payload for `DogAdapter#findHasMany` does not have a
`relationships` key for any of the returned dogs. This is technically
valid JSONAPI, but in today's Ember Data, *for explicit inverses only*
(notice how we specified 'person' on the Dog model; an implicit
relationship would be generated for us if we left it out and used
internally for tracking various things), the inverse relationship does
not get set up. For example, `dog.get('person.id')` would still be null
when it should be `1` (the `id` of the Person it belongsTo).

This commit fixes that by always pushing the inverse relationship using
the store's RelationshipPayloadsManager. In this example, the following
relationship info is pushed:

```javascript
{
  data: {
    id: '1',
    type: 'person'
  }
}
```

This makes the result of requesting `DogAdapter#findHasMany` effectively
the following payload:

```javascript
{
  data: [
    {
      id: 1,
      type: 'dog',
      attributes: { name: 'Scooby' },
      relationships: { id: '1', type: 'person' }
    },
    {
      id: 2,
      type: 'dog',
      attributes: { name: 'Scrappy' },
      relationships: { id: '1', type: 'person' }
    }
  ]
}
```

Adding this information makes the relationships line up correctly.
  • Loading branch information
fivetanley committed Aug 29, 2018
1 parent 2387d63 commit 6c2deac
Showing 1 changed file with 106 additions and 0 deletions.
106 changes: 106 additions & 0 deletions addon/-private/system/store/finders.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,97 @@ export function _findMany(adapter, store, modelName, ids, internalModels) {
);
}

function ensureInverseRelationshipDataOnHasManyResponse(
store,
payload,
leftHandSideRelationshipModel,
relationship
) {
const { modelName: parentModelName, id: parentModelID } = leftHandSideRelationshipModel;
if (payload.data.length === 0) {
return payload;
}

let inverseRelationshipInfo = getInverseRelationshipInfo(
store,
leftHandSideRelationshipModel,
relationship
);
if (inverseRelationshipInfo) {
let relationshipIdentifier = buildInverseRelationshipIdentifier(
inverseRelationshipInfo,
leftHandSideRelationshipModel,
relationship
);

payload.data.forEach(record => {
pushInverseRelationshipInfo(store, record, relationshipIdentifier);
});
}

return payload;
}

// TODO: should we modify the payload or use the store._relationshipsPayload manager?
function ensureInverseRelationshipDataOnBelongsToResponse(
store,
payload,
leftHandSideRelationshipModel,
relationship
) {
let inverseRelationshipInfo = getInverseRelationshipInfo(
store,
leftHandSideRelationshipModel,
relationship
);
if (inverseRelationshipInfo) {
let relationshipIdentifier = buildInverseRelationshipIdentifier(
inverseRelationshipInfo,
leftHandSideRelationshipModel,
relationship
);
pushInverseRelationshipInfo(store, payload.data, relationshipIdentifier);
}
return payload;
}

function getInverseRelationshipInfo(store, leftHandSideRelationshipModel, relationship) {
const { modelName: parentModelName } = leftHandSideRelationshipModel;

let info = store._relationshipsPayloads.getRelationshipInfo(
parentModelName,
relationship.meta.name
);

return info.hasInverse ? info : null;
}

function buildInverseRelationshipIdentifier(
inverseRelationshipInfo,
leftHandSideRelationshipModel
) {
const { modelName: parentModelName, id: parentModelID } = leftHandSideRelationshipModel;

let relationshipInfo = {
id: parentModelID,
type: parentModelName,
};

if (inverseRelationshipInfo.rhs_relationshipMeta.kind === 'hasMany') {
relationshipInfo = [relationshipInfo];
}

return {
[inverseRelationshipInfo.rhs_relationshipName]: {
data: relationshipInfo,
},
};
}

function pushInverseRelationshipInfo(store, jsonApiDoc, relationshipIdentifier) {
store._relationshipsPayloads.push(jsonApiDoc.type, jsonApiDoc.id, relationshipIdentifier);
}

export function _findHasMany(adapter, store, internalModel, link, relationship) {
let snapshot = internalModel.createSnapshot();
let modelClass = store.modelFor(relationship.type);
Expand Down Expand Up @@ -131,6 +222,14 @@ export function _findHasMany(adapter, store, internalModel, link, relationship)
null,
'findHasMany'
);

payload = ensureInverseRelationshipDataOnHasManyResponse(
store,
payload,
internalModel,
relationship
);

let internalModelArray = store._push(payload);

internalModelArray.meta = payload.meta;
Expand Down Expand Up @@ -168,6 +267,13 @@ export function _findBelongsTo(adapter, store, internalModel, link, relationship
return null;
}

payload = ensureInverseRelationshipDataOnBelongsToResponse(
store,
payload,
internalModel,
relationship
);

return store._push(payload);
},
null,
Expand Down

0 comments on commit 6c2deac

Please sign in to comment.