This might be a problem with my API, I'm using Mirage at the moment to fake the data. I'm using the following code on my route handlers:
import Ember from "ember";
export default Ember.Route.extend( {
model() {
return this.store.queryRecord( "chart", { carId: 1, type: "score", period: "daily" } );
},
afterModel( model ) {
console.log( model.get( "carId" ) );
},
} );
The console output is undefined but if I use model.get( "firstObject" ).get( "carID" ) the output is 1 as expected. The promise created by queryRecord resolves in a RecordArray instead of a RecordObject.
The only clue I have about this is that the API call returns an array with one object, but I was expecting queryRecord to get the first record of whatever list it gets back or to pass a special parameter to the backend so it knows it has to return only one result.
I've run into this exact issue. queryRecord will expect a single response back even though it is passing query params to your API. It assumes that your API is aware of some sort of unique constraint and know to return just 1 object. I've worked with a few external API's and found that you almost never get a single object back with a query, versus hitting a show endpoint:
GET api/v1/account/1 <--- single response
GET api/v1/[email protected] <--- array response with 1 object
Even though email is unique, the API is going to give me an array. I made the same assumption that queryRecord just returns the first object but in practice I just use query and later return the first object. If you are writing the API yourself, you could make it return an object if you are querying against a unique field, but that doesn't necessarily seem to be the convention.
¯_(ツ)_/¯
The behavior of queryRecord in ED 2.6.0 didn't make sense to me either until I saw this https://github.com/emberjs/data/commit/5399de0c4820fe26474d667254592c2c5eb7310d.
But it still isn't consistent with
https://guides.emberjs.com/v2.6.0/models/finding-records/#toc_querying-for-a-single-record
and the json-api spec
http://jsonapi.org/format/#document-top-level
A logical collection of resources MUST be represented as an array, even if it only contains one item or is empty..
As @sbatson5 said, we are using query strings instead of identifiers so we will always get a list unless the backend knows somehow that we want a single object. And as you pointed out, the JSON API spec requires this to be this way.
How does that commit make more sense? According to the commit it should be returning a single object or the first of an array if the response is an array. The behaviour is still unexpected and against their own specifications
The commit itself doesn't make sense in terms of the current docs and json-api. The behavior and subsequent error message from ED make sense because the commit changed the expected behavior of queryRecord.
https://github.com/emberjs/data/commit/5399de0c4820fe26474d667254592c2c5eb7310d#diff-fd4c34d9a34dfde73cd3413128a3c973R1056
Oh, ok. It's actually a good starting point to figure out how to fix it. I think the issue is not so much with the API implementation but with the serializer. I'm using Mirage 0.1.13 and I'm suspecting it doesn't implement the queryRecord method on the serializer properly.
I'm in favor of (for what it's worth) queryRecord() just picking the first item in the returned array. That's the way my API will return data, single item in an array rather than the single object. I'm using the REST Adapter but yeah, this functionality seems to line up with how JSON API wants to handle it...
As pointed out in the docs, store.queryRecord() should be used when you are querying for a single record where the id is not known beforehand. The response is expected to only include a single record (aka data: {} in JSON-API). Most likely, store.queryRecord() will be used in combination with overriding adapter.urlForQueryRecord. As an example imagine an endpoint GET /current_user which returns the currently logged in user:
// app/adapters/user.js
import Adapter from './application';
export default Adapter.extend({
urlForQueryRecord(query) {
if (query.current) {
return "/current_user";
}
return this._super(...arguments);
}
});
// later in your routes, controller, ...
store.queryRecord('user', {Â current: true }).then(function(currentUser) {
console.log("id of currently logged in user", currentUser.get("id"));
});
In all other cases you most likely want to use store.query() and return the firstObject of the result:
store.query('user', {Â email: "[email protected]" }).then(function(users) {
return users.get("firstObject");
});
If this use case occurs more often in your app, you could even extend the store locally in your app:
// app/services/store.js
import DS from "ember-data";
export default DS.Service.extend({
queryFirst() {
return this.query(...arguments).then(function(result) {
return result.get("firstObject");
});
}
});
// later in your routes, controller, ...
store.queryFirst('user', {Â email: "[email protected]" }).then(function(uniqueUser) {
console.log("id of user", uniqueUser.get("id"));
});
Or you could install the ember-data-query-first addon to get this method out of the box.
I hope this answer helps to clear up the mystery about store.queryRecord.
Hi @pangratz - I'm running into an issue where queryRecord sends a request to an endpoint (api/users/me), and receives a single object, but still errors and claims it's getting an array.
Here is the request (in app/services/current-user.js):
load() {
if (this.get('session.isAuthenticated')) {
return this.get('store').queryRecord('user', { me: true }).then((user) => {
this.set('user', user);
});
} else {
return RSVP.resolve();
}
}
Here is the response from the API, as taken from the networks tab:
{,…}
data: {type: "User", id: "2", attributes: {username: "admin", first_name: "", last_name: "", email: "",…},…}
attributes:{username: "admin", first_name: "", last_name: "", email: "",…}
id: "2"
relationships:{groups: {data: [], meta: {count: 0}}, user_permissions: {data: [], meta: {count: 0}}}
type: "User"
And here's the error I'm getting:
Assertion Failed: Expected the primary data returned by the serializer for a 'queryRecord' response to be a single object or null but instead it was an array.
The returned value clearly isn't an array. Any idea what's going on?
@cameronblandford likely it is an array. Did you set a breakpoint where the assertion is thrown to see?
I threw down a debugger, inspected it, etc. All curly braces, no brackets, one item, very much an object. Luckily, it was caused by a custom serializer that somebody else added to the codebase which I hadn't noticed, and switching serializers fixed the problem :+1: Apologies for the confusion!
ha! I should have realized that wasn't our assertion, why would queryRecord expect an array ? our assertion is the opposite.