Potential bug in store when hydrating find results

AI-generated summary

A potential bug in the Discourse store has been reported when hydrating find results. kleinfreund questioned the _hydrate() method’s second argument in store.js.es6, suggesting it might cause an error when finding a record by ID. eviltrout explained that the issue arises from the expected JSON format, where the root object should not be the data itself, but rather an object containing the data. For example, {"person": {"id": 123, "name": "robin"}} instead of { "id": 123, "name": "robin"}.

kleinfreund clarified that this means the response JSON for find() should have a singular property (e.g., plugin_route), while findAll() should have a plural property (e.g., plugin_routes). eviltrout confirmed this understanding and pointed to the test suite for examples.

To resolve this issue, a custom adapter can be created to transform the returned JSON into the required format if the server API cannot be modified.

What is the following line doing: discourse/app/assets/javascripts/discourse/models/store.js.es6
?

return this._hydrate(type, result[Ember.String.underscore(type)], result);

In particular, why is the second argument of the _hydrate() method not the result object? Usually, the second argument for this method is the record object that is saved to/retrieved from the store.

Here, when I’m trying to find a record by ID, I will always run into this line:

if (!obj) {
  throw new Error("Can't hydrate " + type + " of `null`");
}

That’s because _hydrate() will be called with result[Ember.String.underscore(type)]. Since my record doesn’t have a property named like the type, that’s always undefined.

Is that a bug?

We require when returning data from the server that the object is not the root of the JSON object. For example instead of:

{ "id": 123, "name": "robin"} it should be {"person": {"id": 123, name: "robin"} }

This is used for cross-referencing records. For example a JSON payload might include multiple relationships and the store will wire them up together.

If you have no control over your server API, you can create a new adapter to transform the returned JSON into the format our code requires.

@eviltrout If I understand this correctly, for an endpoint plugin-route, the method this.store.find('plugin-route') would return a response with a plugin_route property (singular) in the responseJson property, but the method this.store.findAll('plugin-route') would return a response with a plugin_routes property (plural) in the responseJson property?

Likewise, sending a request for /persons, a response object with a property persons in responseJson is expected, correct?

{
  "persons": []
}

Yes that is the idea. You can review a bunch of examples in our test suite:

https://github.com/discourse/discourse/blob/master/test/javascripts/helpers/store-pretender.js.es6