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
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
When I use 'hasMany' in a model referencing ember data (canary) stored in a fixture then I get this error...
Error while processing route: bookings Cannot read property 'typeKey' of undefined TypeError: Cannot read property 'typeKey' of undefined
The other examples I've seen on SO don't seem to be the exact same problem. I've tried to recreate the problem here, if you uncomment the 'hasMany' part in this example then it errors
http://emberjs.jsbin.com/yukahoduco/1/
App.Todo = DS.Model.extend({
body: DS.attr('string')
messages: DS.hasMany('message')
});
App.Message = DS.Model.extend({
user: DS.attr('string'),
subject: DS.attr('string')
});
App.Todo.FIXTURES = [
{
id: 1,
body: 'First Todo',
messages: [{
user: 'Harry',
subject: 'Buy shaving cream'
}]
},
{
id: 2,
body: 'Second Todo',
messages: [{
user: 'Bob',
subject: 'Buy razors'
}]
}
];
Note: I tried this in your fiddle and it returned an error. I don't know if a breaking change occurred in newer versions or if its a bad combination of versions. However, I can tell you this code was tested locally with 1.7.0 and 1.0.0-beta.10 (the defaults for ember-cli 0.1.2)
Fixtures for a model:
Fixtures, for a FixtureAdapter, are not data coming into your application, its data that is already there. So, you are creating data at the class level (not model instance) i.e. you add records as if you were saving rows to a table.
App.Todo.FIXTURES = [
{
id: 1,
body: "First Todo",
messages: [100]
},
{
id: 2,
body: "Second Todo",
messages: [200]
}
];
App.Message.FIXTURES = [
{
id: 100,
"user": "Harry",
"subject": "Buy shaving cream",
todo: 1
},
{
id: 200,
"user": "Bob",
"subject": "Buy razors",
todo: 2
}
];
export default Ember.Controller.extend({
actions: {
new: function() {
var newRecord = this.store
.createRecord('todo', {
body: this.get('newBody'),
messages: [100]
});
}
}
}
);
Then, in your model you setup your relationship like this:
App.Todo = DS.Model.extend({
body: DS.attr('string'),
// We need to set async: true for the FixtureAdapter to load the relations
messages: DS.hasMany('message', { async: true })
});
var Message = DS.Model.extend({
user: DS.attr('string'),
subject: DS.attr('string'),
todo: DS.belongsTo('todo')
});
When not setting a fixture for a model:
If you want to load your data with the format that is shown in the question:
{
id: 1,
body: 'First Todo',
messages: [{
user: 'Harry',
subject: 'Buy shaving cream'
}]
}
You need to setup a serializer (DS.RESTSerializer or DS.JSONSerializer or DS.ActiveModelSerializer) that handles embedded data by passing to it a DS.EmbeddedRecordsMixin during creation. See: http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html
I believe that the following line is causing your issues:
user: DS.belongsTo('string')
There is no String model declared, so this is going to result in the error you posted when the container tries to look it up. The reason it's happening when you include the hasMany relationship on the Todo model is (I think) because that forces the Message model to be loaded, which causes the relationships to be loaded. Without the hasMany relationship, the Message model is never used and the error is never discovered.
I'm trying to set up the attributes to my model but having trouble when nesting objects. Here is what I have
App.Posts = DS.Model.extend({
title: DS.attr('string'),
author: {
name: DS.attr('string')
},
date: DS.attr('date'),
excerpt: DS.attr('string'),
body: DS.attr('string')
});
How am I suppose to declare the author object?
In the ember inspector when I go under data and under App.post and select one of the rows. It has a property a property App.Post with an attribute author: { name: [Object] }
here is the JS Bin link http://jsbin.com/tesozepexaqi/2/
Ember works perfectly fine without Ember Data. Let's pretend we want to do it with Ember Data:
Ember Data's Records should be flat. This means all properties are at the top level. If you have a related data that exist deeper they generally live in a different record. If you're attempting to embed the record you'll need to look into the tricky world of embedded records (Ember-data embedded records current state?). At the very least these related records must have an id defined. So here's an example of what the data returned from server should look like.
{
posts:[
{
id: '1',
title: 'My name is Django.',
author: {
id:1,
name: 'D name'
},
date: new Date('08-15-2014'),
excerpt: 'The D is silent.',
body: 'The D is silent.'
},
{
id: '2',
title: 'White horse',
author: {
id:2,
name: 'horse name'
},
date: new Date('08-15-2014'),
excerpt: 'Is what I ride.',
body: 'My horse likes to dance.'
}
]
}
Code
App.ApplicationAdapter = DS.RESTAdapter.extend();
App.PostSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
author: {embedded: 'always'}
}
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
author: DS.belongsTo('author'),
date: DS.attr('date'),
excerpt: DS.attr('string'),
body: DS.attr('string')
});
App.Author = DS.Model.extend({
name: DS.attr()
});
Example: http://jsbin.com/vazada/1/edit
One other small tip, you'll not want to use globals when working with the routes, you can use modelFor to get the model from a different route.
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('post');
}
});
App.PostRoute = Ember.Route.extend({
model: function(params) {
var posts = this.modelFor('posts');
return posts.findBy('id', params.post_id);
}
});
Personally, I think Ember Data is overkill. Ember works perfectly well with POJOs. If you need caching and the ability to rollback then Ember Data might be a good solution for you.
Example: http://jsbin.com/vazada/2/edit
Adjusting this example from the Ember docs like the below should work:
App.Post = DS.Model.extend({
title: DS.attr('string'),
author: DS.belongsTo('author'),
date: DS.attr('date'),
excerpt: DS.attr('string'),
body: DS.attr('string')
});
App.Author = DS.Model.extend({
post: DS.belongsTo('post')
})
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)
I'd like to display a list populated with data from a JSON file. I would use a modified Adapter. Here's the code so far, including path to the test json file (since I'd hope to get some help without the use of FIXTURES)
I'm unsure on how to pushObject for results using Ember-Data. I know I'm probably still not getting some concepts for Ember-Data.
My question is: How do I get a list from the JSON file based on the following code.
JavaScript
App = Em.Application.create();
App.store = DS.Store.create({
revision: 4,
adapter: App.adapter
});
App.adapter = DS.Adapter.create({
findAll: function (store, type) {
console.log('finding');
var url = type.url;
$.getJSON(url, function(data) {
store.loadMany(type, data);
});
}
});
App.Person = DS.Model.extend({
fName: DS.attr('string'),
surname: DS.attr('string'),
url: 'test.json',
});
App.personController = Ember.ArrayController.create({
content: [],
init: function(){
this._super();// create an instance of the person model
this.set('person', App.store.findAll(App.Person));
},
});
HTML
<script type="text/x-handlebars">
{{#each App.personController}}
<p>{{fName}} {{surname}}</p>
{{/each}}
</script>
EDIT
I´ve updated the code in the jsfiddle on #pauldechov recommendations, still no luck.
Here's the content for test.json, so the whole app can be recreated:
[{"name": "Bruce","surname": "Banner"},
{"name": "Peter","surname": "Parker"},
{"name": "Natasha","surname": "Romanoff"},
{"name": "Steve","surname": "Rogers"},
{"name": "Tony","surname": "Stark"}]
The url property should be defined as such:
App.Person = DS.Model.extend({
fName: DS.attr('string'),
surname: DS.attr('string'),
}).reopenClass({ url: 'test.json' });
...as an instance variable rather than as a class variable (see example: https://github.com/emberjs/data#find). The way you defined it (or if you use .reopen({...}) instead of .reopenClass({...}), it would be accessible via (a specific) person.url rather than type.url.
AFAIK you can't add a .json file to jsfiddle's resources; it won't be accessible. Querying the full url of test.json yielded a cross domain issue.
You may need to place the model(s) above App.adapter, above App.store. Try this if it's still failing.
Peripheral observation: "name" in test.json vs. "fName" in the code?
App.personController = Ember.ArrayController.create({
content: [],
init: function(){
this._super();// create an instance of the person model
this.set('person', App.store.findAll(App.Person));
},
});
Why set person? Should it not be set content?