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
Related
I have been looking at EmberJS tutorials, but all of them use FixtureAdapter, and I'm trying to switch to RESTAdapter, but I'm facing a persistent error
Error: Assertion Failed: Expected an object as `data` in a call to push for Books.Book , but was undefined
here's the code for the adapter:
Books.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'http://localhost:24818/api/'});
and calling the api in the router:
Books.BooksRoute = Ember.Route.extend({
model: function() {
return this.store.find('book',1);
}
});
How do I get store.find to return a JSON to be used in the Handlebars template?
Many thanks,
Edit: this is my API response:
[{"Id":1,"Title":"Pride and Prejudice","Year":1813,"Price":9.99,"Genre":"Comedy of manners","AuthorId":1,"Author":null},{"Id":2,"Title":"Northanger Abbey","Year":1817,"Price":12.95,"Genre":"Gothic parody","AuthorId":1,"Author":null},{"Id":3,"Title":"David Copperfield","Year":1850,"Price":15.00,"Genre":"Bildungsroman","AuthorId":2,"Author":null},{"Id":4,"Title":"Don Quixote","Year":1617,"Price":8.95,"Genre":"Picaresque","AuthorId":3,"Author":null}]
Edit: add model definition:
Books.Book = DS.Model.extend({
title: DS.attr('string'),
year: DS.attr('number'),
price: DS.attr('number'),
genre: DS.attr('string'),
authorId: DS.attr('number'),
author: DS.attr('string')
});
I think you need to normalize your response. i.e
"books": [
{
"id":1,
"Title":"Pride and Prejudice",
"Year":1813,
"Price":9.99,
"Genre":"Comedy of manners",
"AuthorId":1,"Author":null
},
{
"id":2,
"Title":"Northanger Abbey",
"Year":1817,
"Price":12.95,
"Genre":"Gothic parody",
"AuthorId":1,
"Author":null
},
{
"id":3,
"Title":"David Copperfield",
"Year":1850,
"Price":15.00,
"Genre":"Bildungsroman",
"AuthorId":2,
"Author":null
},
]
If you have no control over the endpoint. You will need to setup a serializer or normalizer to normalize your response into this expected format.
--
Useful Links
Ember-data Model Maker
Ember JSON Conventions
REST Serializer
Currently developing an app using Ember-cli and having some dificulties with models with 2 words and relationships.
Model residents-profile
//models/residents-profile.js
DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
picture: DS.attr('string'),
phone: DS.attr('string'),
gender: DS.attr('string'),
residentsAccount: DS.belongsTo('residentsAccount')
}
** Model Residents-account **
//models/residents-account.js
DS.Model.extend({
email: DS.attr('string'),
password: DS.attr('string'),
profileId: DS.attr('number'),
residentsProfile: DS.belongsTo('residentsProfile', this.profileId),
});
the model hook on the residents route:
//routes/residents.js
model: function() {
return Ember.RSVP.hash({
residentsProfile: this.store.find('residentsProfile'),
residentsAccount: this.store.find('residentsAccount')
})
}
As soon as I try and fetch just the residents profile, i get an error "residents.index Cannot read property 'typeKey'"
But if I remove the relationship key from residents-profile and call only the residents profile the data is fetched correctly.
I'm using the RESTAdapter and a Restful API,
The models are being returned separately and the response from the server is as follows:
GET /residentsProfile
{
"residentsProfiles": [{
"id": 20,
"picture": null,
"phone": null,
"firstName": "Rocky",
"lastName": "Balboa",
"blockId": null,
"unitId": null,
"createdAt": "2014-09-17 19:54:28",
"updatedAt": "2014-09-17 19:54:28",
"residentsAccount": [5]
}]
}
GET /residentsAccount
{
"residentsAccounts": [{
"id": 5,
"email": "rocky#balboainc.me",
"admin": false,
"resident": true,
"profileId": 20,
"createdAt": "2014-09-17 19:54:29",
"updatedAt": "2014-09-17 19:54:29",
"residentsProfile": [20]
}]
}
EDIT
Besides the changes proposed by #Kingpin2k, which were spot on, i used setupcontroller in the following way:
setupController: function(controller,
controller.set('residentsAccount', models.residentsAccount);
controller.set('residentsProfile', models.residentsProfile);
}
Now everything works.
Three problems, both of your relationships should be async (since they aren't returned in the same response)
residentsAccount: DS.belongsTo('residentsAccount', {async: true})
residentsProfile: DS.belongsTo('residentsProfile', {async:true})
And both of the json response from them should be a single id, not an array
{
"residentsProfiles":[
{
"id":20,
"picture":null,
"phone":null,
"firstName":"Rocky",
"lastName":"Balboa",
"blockId":null,
"unitId":null,
"createdAt":"2014-09-17 19:54:28",
"updatedAt":"2014-09-17 19:54:28",
"residentsAccount": 5
}
]
}
Lastly, I'm not sure what you're trying to accomplish with this.profileId here, but it probably isn't doing what you think it is. this in that scope is probably the window, meaning you're passing in undefined likely.
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.
The example I have found online show something as follows:
Flashcards.Subject = DS.Model.extend({
name: DS.attr('string'),
flashGroups: DS.hasMany('flashGroup', {async: true})
});
Flashcards.FlashGroup = DS.Model.extend({
name: DS.attr('string'),
subject: DS.belongsTo('subject')
});
And then format JSON as follows:
{"subject": {"id": 1, "name": "foo", "links": {"flash_groups": "/subjects/1/flash_groups"}}}
When I try sumSubject.get('flashGroups'), nothing happens.
I believe there's a bug where links keys are not camelized. Try to return the following JSON:
{"subject": {"id": 1, "name": "foo", "links": {"flashGroups": "/subjects/1/flash_groups"}}}
(use flashGroups instead of flash_groups)
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