I want to try serving sideloaded data to my Ember app. I have a city
model which hasMany
fireStations
. I changed my hasMany
relationship to have an { async: false }
option to coincide with sideloading, since the data will no longer be loaded asynchronously.
I use a custom serializer, and I am logging the response from normalize()
. It looks like this for my data.
{
"data": {
"id": "3",
"type": "city",
"attributes": {
"name": "Anytown USA"
},
"relationships": {
"fireStations": {
"data": [
{
"id": "17",
"type": "fire-station"
},
{
"id": "18",
"type": "fire-station"
}
]
}
}
},
"included": [
{
"id": "17",
"type": "fire-station",
"attributes": {
"name": "North Side Fire Station"
},
"relationships": {}
},
{
"id": "18",
"type": "fire-station",
"attributes": {
"name": "East Side Fire Station"
},
"relationships": {}
}
]
}
I think my sideloaded data is properly formatted. It seems to match the example in the guides. The included
array is populated with all my sideloaded data, and it all seems to be formatted as needed.
However, I'm hitting this error in my app.
Assertion Failed: You looked up the 'fireStations' relationship on a 'city' with id 3 but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async ('DS.hasMany({ async: true })')
According to the JSON API spec, the format I am using seems to be correct for loading compound documents.
In a compound document, all included resources MUST be represented as an array of resource objects in a top-level
included
member.
And it seems links are optional, so it should be fine that I am leaving them out.
The optional
links
member within each resource object contains links related to the resource.
I can't figure out what the issue is here. I forked ember-data locally and I do indeed see this assertion is triggered.
If I manually loop over the manyArray
in has-many.js
, I see that each record is marked as isEmpty
being true
Why are the has-many
records returning isEmpty === true
? What might I be doing wrong that is preventing sideloading from working correctly?
I was able to create an Ember Twiddle to find, recreate, and resolve my issue.
Short answer is that my custom serializer was dropping included
during normalization. I was confusing the responsibilities between normalize() and normalizeResponse() and returning included
from the wrong method.
I was trying to set the included
property on the hash in normalize()
(that's what you see in my question above). This approach was entirely wrong.
included
should be present in the response from normalizeResponse()
, if needed. So I (superfluously) had included
in response()
, but didn't have it where it was actually needed in normalizeResponse()
.
normalizeResponse (store, primaryModelClass, payload, id, requestType) {
let { data, included } = this._super(...arguments);
// Do some manipulation on `data` or `included`.
// Oh no! We lost the `included` array!
// This will cause an error
// because we are saying the
// relationship is not async, but we're
// dropping the `included` data!
return { data };
// This is what we want, for this contrived example.
// return { data, included };
}
If an included
array needs to be returned, it should be returned from normalizeResponse()
, not normalize()
. This should be obvious to anyone looking at the source code for the JSONAPISerializer in json-api.js
, but I somehow got lost along the way.
In the JSONAPISerializer implementation for ember-data 2.17.0, you can see that included
is set on the documentHash
in a method invoked by normalizeResponse()
.
My serializer does a lot of customization since my API is very non-standard, and I misunderstood some of the details here. Also, I think it's a bit difficult to understand what should and shouldn't be done in each of the various normalize...
API hooks, despite the docs. Though, I think if I spent more time reading them, I probably would've understood this.