I have a single call to the server that returns the whole data of my application
[
{
"id": 1,
"count": 0,
"canGroup": true,
"childs": {
"group": [
{
"id": 0,
"count": 3,
"canGroup": true,
"childs": {
"user": [
{
"id": 0,
"count": 3,
"canGroup": true
}
...
]
}
] ...
}
...
]
How can I do to deserialize this model with Ember Data?
There are two parts to this question. One is how to structure your model to represent this data, the second is how to modify the incoming data so Ember can handle it. The model, let's call it group, would be something like:
// models/group.js
export default DS.Model.extend({
count: DS.attr(),
canGroup: DS.attr(),
childs: DS.hasMany('group')
});
Now we need to tell Ember Data that the children are going to be coming in embedded inside the parent. To do that, we mix in DS.EmbeddedRecordsMixin into the serializer, and also specify an attrs hash, as follows:
// serializers/group.js
export default DS.RESTSerializer(DS.EmbeddedRecordsMixin, {
attrs: {
childs: { embedded: 'always' }
}
However, there is one remaining problem. By default, Ember Data will expect that the childs property contains an array of, well, children. Instead, your data has childs containing a group property which contains the array. We need to fix that up, which we will do by overriding normalize:
normalize(type, hash, prop) {
hash.childs= hash.childs.group;
return this._super(type, hash, prop);
}
});
There are other issues to worry about, including whether you want Ember Data to serialize the embedded children (when writing back to the server), but the above should be enough to get you started.
By the way, I notice you have two different groups with an id of 0. That is going to greatly confuse Ember Data. Try to have unique IDs. If that is not possible, you might consider synthesizing IDs with additional serializer logic.
Related
I'm still learning ember.js and have run into a roadblock with ember data not resolving lookup relationships in models. I have one model 'site' that will be basically a lookup table for every other model to differentiate data based on location.
At this point, I'm doing something wrong or missing a key concept - probably both... (or maybe it's the wee hour!)
Site Model (i.e. the lookup table)
import DS from 'ember-data';
export default DS.Model.extend({
code: DS.attr(),
name: DS.attr(),
});
The site model would have a hasMany relationship to all my other models (will be about 12 when complete)
Associate Model
import DS from 'ember-data';
import { belongsTo } from 'ember-data/relationships';
export default DS.Model.extend({
site: belongsTo('site'),
last: DS.attr(),
first: DS.attr(),
active: DS.attr('boolean'),
fullName: Ember.computed('first', 'last', function() {
return `${this.get('first')} ${this.get('last')}`;
}),
});
The 'associate model' will also be a lookup along with 'site' in some other models.
I'm providing data via the JSON API spec but I'm not including the relationship data because as I understand it, ember data it should be pulling down the site data using the site id attribute.
{
"links": {
"self": "/maint/associates"
},
"data": [
{
"type": "associate",
"id": "1",
"attributes": {
"site": "6",
"last": "Yoder",
"first": "Steven",
"active": "1"
},
"links": {
"self": "/associates/1"
}
}
]
}
In my template file I'm referencing associate.site which gives me an error.
<(unknown mixin):ember431>
If I use associate.code or .name to match the site model, nothing will show in the template. The code from the 'site' table is the data I really want to displayed in the template.
So the obvious questions:
Am I wrong that Ember Data should be resolving this or do I need to
include the relationship in my API response?
I realize that my belongsTo in the 'associate' model only references
site while I want site.code, so how do I make that relationship
known or access the field in my 'associate' model?
I didn't include hasMany relationship in the 'site' model because
there would be many. Do I need to do an inverse relationship in
other models? Examples I've seen don't all show the hasMany
relationships setup.
When I look at the models in ember inspector the site field is not
included in the model. Even if I wasn't getting the correct data
should it still show up?
I like ember so far, just need to understand and get over this roadblock
Update: My backend JSON library would only generate relationship links based on the current spec which would be
"related": "/streams/1/site"
but ember data does call
"related": "/sites/1"
to resolve the relationship
So #Adam Cooper answer is correct if you generate links as he answered or if you can only generate the links based on the current specification.
If you're using the JSONAPIAdapter, which is the default, you want your response to look this:
{
"links": {
"self": "/maint/associates"
},
"data": [{
"type": "associate",
"id": "1",
"attributes": {
"last": "Yoder",
"first": "Steven",
"active": "1"
},
relationships: {
"site": {
"links": {
related: "/sites/6"
}
}
}
}]
}
That will allow Ember Data to look up the site via its relationship. Right now Ember is trying to access the site model which Ember Data can't populate hence the error you're getting. As an aside you could probably do with returning an actual boolean value for active too.
I want ember to list all foo's on {{#each foo}} even though the id's are the same.
This is my model.
App.Item = DS.Model.extend({
title: DS.attr("string"),
foo: DS.hasMany("foo", {async: true})
});
App.Foo = DS.Model.extend({
bar: DS.attr("string")
});
Let's say that foo is foo: [1] and the json response for that is:
{
"foos": [{
"id": 1,
"bar": "But is it _lightweight_ omakase?"
},
{
"id": 1,
"bar": "I for one welcome our new omakase overlords"
},
{
"id": 1,
"bar": "Put me on the fast track to a delicious dinner"
}]
}
Ember only lists the last object instead of everyone. I know that ember wants different ids, but is there a way to force ember to list all objects with same ids?
I'm using ember data and this is a read-only app.
EDIT
With different ids on the objects it still doesn't work. Foo can still only have 1: foo:[1] though. So that's the problem.
My problem is that I can't list all the id's on foo. So I need a way to get ember to show whatever comes back from the request whether it knows about the ids or not.
I know that ember wants different ids, but is there a way to force ember to list all objects with same ids?
The short answer is no. IDs are identifiers, specifically unique identifiers with Ember Data. You can't have two objects with the same type and ID. Give them different IDs and everything will work fine.
I'd advise taking real care with plurality, it'll keep things much clearer.
I don't really understand what you're looking to do here, but I'd suggest it might be one of these two things.
a) Each "item" has many "foos". In which case I'd change slightly:
App.Item = DS.Model.extend({
title: DS.attr("string"),
foos: DS.hasMany("foo", {async: true})
});
App.Foo = DS.Model.extend({
bar: DS.attr("string")
item: DS.belongsTo('item', {async: true})
});
And get each "foo" from the server.
{
"foos": [{
"id": 1,
"bar": "But is it _lightweight_ omakase?"
},
{
"id": 2,
"bar": "I for one welcome our new omakase overlords"
}...
You can then display each "foo"'s "bar" with:
{{#each item in model}}
{{item.title}}
{{#each foo in item.foos}}
{{foo.bar}}
b) If each "item" has just one "foo" you'll still need different ids unless you just keep the one and update it. Maybe keep a separate archive table if its important? Otherwise maybe maintain an extra attribute in your "foo" model along the line of "isCurrent" or "isValid". I'll admit I have no idea what best practice is here.
Have you considered switching to a URL based model... – GJK
This is exactly what I was after. I didn't notice the findHasMany method in my search. More info here.
Instead of foo: [1] use links: { foo: "http://example.org/foo/1" } And ember will list the foos without having to list all the ids on foo.
My data structure would be something like this.
{
product: {
brand: "",
category: "",
rows: [
{model: "", price: 123},
{model: "", price: 345}
]
}
}
App.ListTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
I want to know what's the difference between Transform list and hasMany and belongs to definition.
Ember strongly supports the concept of a long living web page (SPA). That being said client side you might view the same record multiple times. Ember Data has a store where it keeps track of records (by their primary key, generally id).
General Example of the benefit of Ember Data
Request product with id 1
The store will make a call to your server, wait for response... then build up the record and have it available.
Later, Request product with id 1
The store immediately returns the already fetched record giving your user an excellent user experience.
Why this is important
Let's take your rows above and assume they really are different products. Once you add them to your cart you really want to keep track of model v2, id 987. You then want to keep track of this, not just some string attached in json.
I have the following user object (json):
{
"_id": "my-id",
"org": 666,
"type": "user",
"properties": {
"first_name": "Firstname",
"surname1": "Surname1",
"surname2": "Surname2",
"allowed_apps": [ "one-app", "another-app" ],
"default_app": "one-app",
"email": "email#address.com"
},
"outputs": {
"extension": "extension-id"
}
}
This is a single model, with a complex structure. It is not a "multi-model structure". Therefore, I want to define this using DS.Model.extend, but I do not want to use belongsTo relationships for neither properties nor outputs, since they do not refer to other models: these fields are just complex data, direct part of my user object. That is, we do not have any properties model, nor any outputs model. These are just parts of the user model, and as such are stored in the database (couchdb in our case).
Can this be done in ember?
Lamentably this kind of structure is not possible using ember-data models.
Every value of a model that is not a primitive one such as string, number, boolean & date can't be defined as a model property without designing it with belongsTo or hasMany. Furthermore this site jsonapi.org which is still a WIP describes the direction ember-data is going with it's implementation.
So the point is here if you want/need to use ember-data models (DS.Model) your server should obey the JSON format ember-data expects, otherwise you have always the possibility (since ember-data is backend agnostic) to not use ember-data models definition at all, this way your models can be structured the way you want, but then you are out of being helped from the conventional work ember-data adapter and serializer does for you and you have to write your own adapter/s which then deals with all the peculiarities your JSON's have and load them finally into your store.
If you absolutely need a custom data structure to be exchanged with your backend, you can register a custom transform like for example:
App.Adapter.registerTransform('mega', {
deserialize: function(serialized) {
//do your custom deserialization
},
serialize: function(deserialized) {
//do your custom serialization
}
});
and then use it like:
App.MyModel = DS.Model.extend({
megaValue: DS.attr('mega');
});
Hope it helps.
RESTAdapter has built in attribute types of string, number, boolean, and date. There are relations to link another models to represent some complex data.
To represent array I need to use transformation or change API from something like this:
["ember.js", "angular.js", "embergular.js"]
to:
[
{
"id": 1,
"ember.js"
},
{
"id": 2,
"angular.js"
},
{
"id": 3,
"embergular.js"
}
]
Which is a little bit overkill... Why there is no built in types like array and object?
IMO the main reason why there aren't such attribute type like array or object is mainly per design.
To represent array I need to use transformation or change API from something like this:
but to represent an array without the needs of building a relation with models you could define a custom transform (what you already mentioned) which don't touches your data. For example to use an array as a model attribute you could do something like this:
DS.RESTAdapter.registerTransform('rawData', {
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
Then define it in your model like this:
App.MyModel = DS.Model.extend({
myArray: DS.attr('rawData')
});
This way the attribute myArray will be just what your backend returned, an array or object etc.
Hope it helps.