I am using Ember Data 1.0 (beta 1) and am somewhat confused by the default behavior of associations in ED.
Assume a Book model that has many (hasMany) chapters with each chapter belonging to (belongsTo) a book. My expectation was that the instances of a book's chapters property would automatically have a reference to the book instance through their belongsTo association, but this appears not to be the case. Is this indeed the default behavior in Ember Data or am I overlooking something?
If this is indeed the default behavior, does this mean that I need to create a custom serializer to accomplish this?
No ember-data will not do this for you but it should be possible to achieve. In both cases ember-data will sideload those properties. (In past versions you could setup a mapping so those would be embedded, but you no longer can do this) In your example you would have the following:
App.Book = DS.Model.extend({
chapters: DS.hasMany('Chapter')
});
App.Chapter= DS.Model.extend({
book: DS.belongsTo('Book')
});
Once those are setup ember-data by default will look for a data structured like this:
{
"book": {
"id": "1"
"chapters": ["1", "2", "3"]
},
"chapters": [
{
"id": "1",
"book": "1"
},
{
"id": "2",
"book": "1"
},
{
"id": "3",
"book": "1"
}
]
}
If your data is not in that format and you can not change it then you can extend the extractSingle or extractArray methods on the serializer for that type. At the bottom of this link you can find some more info on that. Also remeber it looks for it in camelcase so you may also need to normalize the json object as well.
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'm working on an Ember app that consumes an external API and I'm having trouble with my serializer.
My app is ingesting a user account but only cares about the very basics of the user account. The API is publishing much more data, which is causing trouble. Specifically, it is including a relationship to a model which I don't include in my Ember App (because it is domain-inappropriate).
I'm trying to configure my app such that it simply ignores the included and relationship information from the API, but even attempting to delete them from the response in the serializer's normalize hook isn't working. It keeps trying to look up the unwritten model and, obviously, failing. As well, I looked at using DS.EmbeddedRecordsMixin but that seems to still depend on having the related model implemented in my app, which I'm really hoping to avoid!
How do I get JSONAPISerializer to ignore relationships and prevent it from attempting to sideload data from the included object?
To better display my situation, here is an example payload:
{
"jsonapi": {
"version": "1.0"
},
"included": [
{
...references models my app doesn't have
}
],
"data": {
"type": "account",
"relationships": {
"permissions": {
"data": [
...references the included object
]
},
},
"id": "16",
"attributes": {
...this is the information I *do* care about
}
}
}
ETA:
My User model:
// app/models/account.js
export default Model.extend(Validations, {
birthdate: attr('date'),
email: attr('string'),
firstName: attr('string'),
lastName: attr('string'),
profile: belongsTo('profile')
}
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.
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.
EmberJS has removed hasOne in the earlier revision. Whats the way to create such a nested object relation where I want to have hasOne
Removal of hasOne has been done in favor of belongsTo, can anyone share a thought on how to write {embedded : always} relation between nested JSON.
I know that this question is old and answered, but since it is one of the top
search results for "ember hasone" i wanted to share my findings on the subject.
I've read the link in the first answer but the example is kinda outdated.
The "embedded" flag is obsolete, "DS.RESTAdapter.map" is not a function and the "DS.hasOne" method deprecated.
The current 1.0.0-beta.2 solution for emulating the "hasOne relationship" is simply using "DS.belongsTo". They are not very different and you just need to add the hasOne foreignKeys to your result-set just like you would with belongsTo.
Source: https://github.com/emberjs/data/commit/a466741a36731c5d382df33461268024627325ef
Here's an example server response from a complex model.
{"users": [{
"id": 1,
"name": "John Doe",
"profile": 27, // merged hasone
"image": 3, // merged hasone
"account_id": 64 // an actual belongsTo
}]}
And then as model
App.User = DS.Model.extend({
name: DS.attr('string'),
profile: DS.belongsTo('profile'),
image: DS.belongsTo('image'),
account_id: DS.belongsTo('account')
});
Hope this helps anyone looking for info on how to model a hasOne
You have to set the mapping on the adapter, please see this answer for a working example.