Maybe I'm overlooking something, but I from can't figure out how I'm supposed
I have a JSON coming from the server which looks like this
{
"articles": [
{
"user": {
"name": "user",
"username": "user",
"_id": "52755cba74a1fbe54a000002"
},
"_id": "5275698c53da846e70000001",
"__v": 0,
"content": "content",
"title": "title",
"created": "2013-11-02T21:07:24.554Z"
}
]
}
In the template, I'm accessing content and created fine, but when I try to get user.name, nothing comes out:
{{#each article in model}}
<span>{{ article.title }}</span>
<span>by {{ article.user.name }}</span>
<span>{{ article.created }}</span>
{{/each}}
I noticed that in the model, whatever I don't define, won't appear in the template, so it looks like this:
title: DS.attr('string'),
content: DS.attr('string'),
created: DS.attr('date')
But when I try to add:
user: {
name: DS.attr('string')
}
To match the nested json, I get an error.
Is ember not able to handle nested json? If it is, then how?
Thanks.
This might be the easiest way to support embedded documents (if you don't want to change your JSON):
App.ArticleSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
user: {embedded: 'always'}
}
})
See: http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html. I haven't tested it though!
As mentioned there are heavy changes to embedded records since Ember Data 1.0 beta 1 and there is no more support for it. If you need embedded records you could have a look at ember-data-extensions project: https://github.com/pixelhandler/ember-data-extensions It provides support for embedded record by an EmbeddedAdapter and EmbeddedRecordMixins.
A simple implementation example:
App.ApplicationAdapter = DS.EmbeddedAdapter.extend({});
App.ApplicationSerializer = DS.EmbeddedSerializer.extend();
App.Article = DS.Model.extend({
user: DS.belongsTo('user'),
content: DS.attr('string'),
title: DS.attr('string'),
created: DS.attr('date')
});
App.User = DS.Model.extend({
name: DS.attr('string'),
username: DS.attr('string'),
articles: DS.hasMany('article')
})
App.ArticleSerializer = App.ApplicationSerializer.extend({
attrs: {
user: {embedded: 'always'}
}
});
But you should be aware that there is no support for embedded records only on load yet.
According to the transition guide; Ember data doesn't support embedded records yet.
https://github.com/emberjs/data/blob/master/TRANSITION.md#embedded-records
There is an example of how to implement it yourself. Another option is to use ember-model, it supports embedded associations. I am sorry I don't have an example.
Related
Pardon me for coming up with this title but I really don't know how to ask this so I'll just explain.
Model: Group (has) User (has) Post
Defined as:
// models/group.js
name: DS.attr('string'),
// models/user.js
name: DS.attr('string'),
group: DS.belongsTo('group')
// models/post.js
name: DS.attr('string'),
user: DS.belongsTo('user'),
When I request /posts, my server returns this embedded record:
{
"posts": [
{
"id": 1,
"name": "Whitey",
"user": {
"id": 1,
"name": "User 1",
"group": 2
}
}
]
}
Notice that the group didn't have the group record but an id instead.
With my serializers:
// serializers/user.js
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
group: {embedded: 'always'}
}
});
// serializers/post.js
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
user: {embedded: 'always'}
}
});
This expects that the User and Post model have embedded records in them. However, it entails a problem since in the json response doesn't have an embedded group record.
The question is, is there a way I can disable embedding records in the 3rd level?
Please help.
Here is a good article about serialization:
http://www.toptal.com/emberjs/a-thorough-guide-to-ember-data#embeddedRecordsMixin
Ember docs:
http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html
Basically, what it says is that you have 2 options 1) serialize and 2) deserialize. Those two have 3 options:
'no' - don't include any data,
'id' or 'ids' - include id(s),
'records' - include data.
When you write {embedded: 'always'} this is shorthand for: {serialize: 'records', deserialize: 'records'}.
If you don't want the relationship sent at all write: {serialize: false}.
The Ember defaults for EmbeddedRecordsMixin are as follows:
BelongsTo: {serialize:'id', deserialize:'id'}
HasMany: {serialize:false, deserialize:'ids'}
I have two models:
App.Offer = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
products: DS.hasMany('product')
});
App.Product = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
offer: DS.belongsTo('offer')
});
And the server is answering with record and array of ids in this way (for example if the rest adapter asks for /offers/1):
{ "offer": [ { "id": 1, "name": "aaaaaaaaa", "description": "aaaa", "product_ids": [ 1, 2 ] } ] }
but now how can I get the products? I have a route like this:
App.OffersRoute = Ember.Route.extend({
model: function() {
var offer = this.get('store').find('offer', 1);
return offer
}
});
In Ember guide is written that if you want the products you should do:
offer.get('products');
Ok, but where should I put this? in the model hook? in a Controller property?
I've tried many things but I can see no network request to products?id[]=1&id[]=2 as I expected (the server is responding correctly to this request);
Can someone please give an example showing how I can find an offer, its products and use this data in my template?
If you're using the RESTAdapter your data needs to be in this format (if you don't want to return it in this format you can create a custom serializer and fix up the json).
2 differences:
the item under offer shouldn't be an array, since you were looking for a single item it should be an object
the key product_ids should be products, product_ids is the format that the ActiveModelAdapter/ActiveModelSerializer use.
JSON
{
"offer":
{
"id":1,
"name":"aaaaaaaaa",
"description":"aaaa",
"products":[
1,
2
]
}
}
The hasMany relationship should be marked as async if you're expecting it to be returned in a separate payload.
App.Offer = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
products: DS.hasMany('product', {async:true})
});
I hooked it up in jsbin below, but I didn't hook up a result from products?ids[]=1&ids[]=2 (note ids[]=, not id[]=), if you check the network tab you'll see the request being issued (but it'll crash since there is no result).
http://emberjs.jsbin.com/OxIDiVU/345/edit
I'm having trouble loading some related models with ember-data. I'm pretty sure this is a syntax error on my part, but I just can't see it. Perhaps someone could be a second set of eyes for me?
The specific problem is that related models are not being displayed in my template.
Without further ado:
Notes
I'm using ember-appkit-rails.
This is my first ember project, so please forgive my n00bness.
I have a feeling that the answer may have something to do with embedded: 'always', but I have no idea how to integrate that into my code.
DEBUG: Ember : 1.4.0 ember.js?body=1:3462
DEBUG: Ember Data : 1.0.0-beta.7+canary.f482da04 ember.js?body=1:3462
DEBUG: Handlebars : 1.3.0 ember.js?body=1:3462
DEBUG: jQuery : 1.11.0 ember.js?body=1:3462
The problematic template
I'd think that this should work. It's almost right out of the ember-appkit-rails generators. The librettist and composer names are not appearing.
<h3>{{id}}</h3>
<p>{{link-to 'Edit' 'operas.edit' this}} <button {{action destroyRecord this}}>Destroy</button></p>
<ul>
<li>Title: {{title}}</li>
<li>Alternate Title: {{alternate_title}}</li>
<li>Source: {{source}}</li>
<li>Librettist: {{librettist.name}}</li>
<li>Composer: {{composer.name}}</li>
<li>Notes: {{notes}}</li>
</ul>
Models
opera.js.es6
export default DS.Model.extend({
title: DS.attr('string'),
alternateTitle: DS.attr('string'),
source: DS.attr('string'),
librettistId: DS.attr('number'),
composerId: DS.attr('number'),
notes: DS.attr('string'),
composer: DS.belongsTo('composer'),
librettist: DS.belongsTo('librettist')
});
librettist.js.es6
export default DS.Model.extend({
name: DS.attr('string'),
operas: DS.hasMany('opera')
});
composer.js.es6
export default DS.Model.extend({
name: DS.attr('string'),
operas: DS.hasMany('opera')
});
ActiveModel Serializer
class OperaSerializer < ActiveModel::Serializer
embed :ids, include: true
attributes :id, :title, :alternate_title, :source, :librettist_id, :composer_id, :notes
has_one :composer
has_one :librettist
end
Sample JSON being returned
This is what I see when I look at one opera record, but the structure holds for the entire set:
{
"composers": [
{
"id": 4097,
"name": "Müller, Wenzel"
}
],
"librettists": [
{
"id": 1414,
"name": "Bäuerle, Adolf"
}
],
"opera": {
"alternate_title": "oder Wien in einem anderen Weltteile",
"composer_id": 4097,
"id": 4166,
"librettist_id": 1414,
"notes": "these are some notes",
"source": "F116.Theater.a.d.Wien.260A.Mus; Mus.Hs.78.Mus; Mus.Hs.25409.Mus",
"title": "Aline Königin von Golkonda"
}
}
I've also tried this approach with inline embedding ({opera: {...composer: {...}}}) but that didn't work any better.
Thanks for your help!
Paul.
the _id are not necessary if you're using the RESTAdapter as your client-side adapter.
App.Opera = DS.Model.extend({
title: DS.attr('string'),
alternateTitle: DS.attr('string'),
source: DS.attr('string'),
notes: DS.attr('string'),
composer: DS.belongsTo('composer'),
librettist: DS.belongsTo('librettist')
});
App.Librettist = DS.Model.extend({
name: DS.attr('string'),
operas: DS.hasMany('opera')
});
App.Composer = DS.Model.extend({
name: DS.attr('string'),
operas: DS.hasMany('opera')
});
JSON
{
"composers": [
{
"id": 4097,
"name": "Müller, Wenzel"
}
],
"librettists": [
{
"id": 1414,
"name": "Bäuerle, Adolf"
}
],
"opera": {
"alternate_title": "oder Wien in einem anderen Weltteile",
"composer": 4097,
"id": 4166,
"librettist": 1414,
"notes": "these are some notes",
"source": "F116.Theater.a.d.Wien.260A.Mus; Mus.Hs.78.Mus; Mus.Hs.25409.Mus",
"title": "Aline Königin von Golkonda"
}
}
http://emberjs.jsbin.com/OxIDiVU/233/edit
If you're using the ActiveModelAdapter then you'd use the _id in the json, here's an example with the ActiveModelAdapter
http://emberjs.jsbin.com/OxIDiVU/234/edit
I used commit eaa1123 (ember) and 508479d (ember-data) to build the JS files.
I have the following JSON returned from my Rails backend, which is generated with active_model_serializers (0.6.0):
{
"posts": [
{
"id": 408,
"title": "Lorem Ipsum",
"body": "In at quo tempora provident nemo.",
"comments": [
{
"id": 956,
"body": "Quo incidunt eum dolorem."
},
...
]
}
]
}
and the following Ember models:
App.Post = DS.Model.extend({
title: DS.attr('string'),
body: DS.attr('string'),
comments: DS.hasMany('App.Comment', {
embedded: true
})
});
App.Comment = DS.Model.extend({
body: DS.attr('string'),
post: DS.belongsTo('App.Post')
});
All look perfectly normal:
post = App.Post.find(408);
post.get('title')
// => "Lorem Ipsum"
However, I can't seem to get to the comments:
comments = post.get('comments')
comments.get('firstObject') instanceof App.Comment
// => true
comments.forEach(function(comment) {
console.log(comment.get('body'))
})
//=> undefined
When I use:
comments.content
I get an Array that contain objects, so:
comments.content[0]
//=> { body: "Quo incidunt eum dolorem.", id: 956 }
but this is not what I expected.
It seems so obvious, so I must be doing something wrong.
As a side-effect: currently I'm not able to render my comments in a template in a easy way, so I hope someone can help me on this one.
Thanks in advance.
If you used that commit it means you are on the latest ember-data revision, which is 11. Adding embedded: true to load an embedded association was deprecated a while back between revision 5 or 9, not too sure again.
If you are using the default restAdapter, you now need to define embedded loading as a mapping as shown below and not as an association option:
App.store = DS.Store.create({
revision: 11,
adapter: DS.RESTAdapter.create()
});
App.store.adapter.serializer.map('App.Post', {
comments: {embedded: 'load'}
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
body: DS.attr('string'),
comments: DS.hasMany('App.Comment')
});
App.Comment = DS.Model.extend({
body: DS.attr('string'),
post: DS.belongsTo('App.Post')
});
You can follow all the previous discussion on it through the links below:
https://github.com/emberjs/data/issues/504#issuecomment-11256934
https://github.com/emberjs/data/pull/430#issuecomment-10925506
Various fixes for loading embedded records:
https://github.com/emberjs/data/pull/541
This not directly related but incase all i wrote above fails, then add this solution to the mix
BelongsTo association are not materialized when using findAssociation and extractHasMany hooks for async HasMany:
https://github.com/emberjs/data/issues/525
The internals for anyone who wants to quickly see where things are defined with respect to the call to 'App.store.adapter.serializer.map'
When we called 'App.store.adapter.serializer.map', the call to serializer is defined on line 536 below and the map is online 696 in the 2nd link
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/adapter.js#L536
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/adapter.js#L696
On line 67 of the DS.RESTAdapter which extends DS.Adapter, the serializer property is made to point to DS.RESTSerializer where additional functionality specific to the RestAdapter are added.
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/adapters/rest_adapter.js#L67
I am working on an application using ember.js and a couch DB backend. So far, i used ember-resource as database driver, but I am considering switching to ember-data, since this seems to be more sustainable.
Since I am working with couch DB, I am using the Couch DB-Adapter.
The documents in my database contain complete object structures, so I have to specify embedded objects in the database driver.
But although I am specifying my sub-objects as embedded, ember-data seems to fetch these objects with separate requests, instead of just getting them out of the main json.
My object definitions are as follows:
App.UserProfile = DS.Model.extend({
type: DS.attr('string'),
fullname: DS.attr('string'),
email: DS.attr('string'),
pictureUrl: DS.attr('string'),
social: DS.hasMany('App.SocialWebAccount', { embedded: true }),
.....
});
App.SocialWebAccount = DS.Model.extend({
profile: DS.belongsTo('CaiMan.UserProfile'),
site: DS.attr('string'),
account: DS.attr('string'),
.....
});
and the server data ist something like this:
{
"_id": "thoherr",
"_rev": "55-d4abcb745b42fe61f1a2f3b31c461cce",
"type": "UserProfile",
"fullname": "Thomas Herrmann",
"email": "test#thoherr.de",
"pictureUrl": "",
"social": [
{
"site": "socialFacebook",
"account": "thoherr"
},
{
"site": "socialXing",
"account": "Thomas_Herrmann7"
},
{
"site": "socialEmail",
"account": "test#thoherr.de"
}
]
}
After loading, the UserProfile does contain an ArrayProxy for my social data, which is populated by three entries, but they are all undefined instead of instances of SocialWebAccount!
If i try to access this array, ember-data seems to do a separate database access to fetch the data, which then leads to an error because the couch DB-adapter accesses an _id field, which is not available in undefined....
What am i missing?
I thought the "embedded" flag signals that the data is already in the json and the objects can be instantiated from the json?
Why does ember-data try to fetch the embedded data?
Any hint?
It seems that the embedded option has changed recently. I found some information in the test files on the ember-data github.
In these test files, the embedded content is defined like this
Comment = App.Comment = DS.Model.extend({
title: attr('string'),
user: DS.belongsTo(User)
});
Post = App.Post = DS.Model.extend({
title: attr('string'),
comments: DS.hasMany(Comment)
});
Adapter = DS.RESTAdapter.extend();
Adapter.map(Comment, {
user: { embedded: 'always' }
});
or
Adapter = DS.RESTAdapter.extend();
Adapter.map(Comment, {
user: { embedded: 'load' }
});
'always' seems to be used for embedded data without ids (your case),eg
id: 1,
title: "Why not use a more lightweight solution?",
user: {
name: "mongodb_expert"
}
'load' seems to be used for embedded data with ids
id: 1,
user: {
id: 2,
name: "Yehuda Katz"
}
Hoping it will help in your particular case. I've had a lot of trouble with hasMany relationships recently (I had to modify my adapter to get it work)