I've been working with Ember Data for a while now and I'm mostly happy with it. I like the way that it has forced me to structure my REST api in a sensible way.
I'm coming into a few problems tho, here's one of them: I was using a model with a flat structure before:
App.Pageview = DS.Model.extend({
event: attr('string'),
value: attr('string'),
time: attr('date'),
count: attr('number')
});
In this case value was a url all in one string. This model would work fine with JSON that would look like this:
{
"event" : "pageview",
"value" : "google.com/some/page",
"time" : "2013-01-31T16:30:00.000Z",
"count" : 14
}
I've changed the way the JSON is structured in the database to make it easier to query so now it looks like this:
{
"event" : "pageview",
"value" : {
"url" : "google.com/some/page",
"domain" : "google.com",
"path" : "/some/page"
},
"time" : "2013-01-31T16:30:00.000Z",
"count" : 14
}
But now i don't really know where to go with things. I tried doing things like this:
App.Pageview = DS.Model.extend({
event: attr('string'),
value: {
domain: attr('string'),
url: attr('string'),
path: attr('string')
},
time: attr('date'),
count: attr('number')
});
But that didn't seem to work, I tested it like this:
console.log(item.get('value'));
console.log(item.get('value.domain'));
// console.log(item.get('value').get('domain')); // throws
console.log(item.get('value').domain);
And got the following results: http://d.pr/i/izDD
So I did some more digging and found that i should probably do something like this:
App.Pageview = DS.Model.extend({
event: attr('string'),
value: DS.belongsTo('App.SplitUrl', {embedded:true}),
time: attr('date'),
count: attr('number')
});
App.SplitUrl = DS.Model.extend({
domain: attr('string'),
url: attr('string'),
path: attr('string')
});
But this doesn't work because of an error i get:
Uncaught TypeError: Cannot call method 'hasOwnProperty' of undefined
This usually happens when I have sent an object down my REST api without an id. Here is the exact response from my REST api:
http://d.pr/n/UyW0
Note: i've overloaded the Adapter so that it expects the id to be in the _id field so that it can work better/easier with mongodb. This is how I have overloaded the Adapter:
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.create({
serializer: DS.JSONSerializer.extend({
primaryKey: function(type) {
return '_id';
}
}),
namespace: 'api'
})
});
Can anyone help me figure this one out?
Assuming you're using ember-data revision 11, try to configure your value as embedded in the adapter instead.
DS.RESTAdapter.map('App.SplitUrl',{value:{ embedded:'always'}});
Related
I'm using Ember 2.5.0 and I have two models service and availability which looks like:
// availability
import DS from 'ember-data';
export default DS.Model.extend({
day: DS.attr('string'),
enabled: DS.attr('boolean'),
startAt: DS.attr('string'),
endAt: DS.attr('string'),
service: DS.belongsTo('service')
});
And service which looks like:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
availabilities: DS.hasMany('availability',
{
defaultValue:
[
{
day: 'saturday',
enabled: false,
startAt: '',
endAt: ''
},
{
day: 'sunday',
enabled: false,
startAt: '',
endAt: ''
}
]
}
)
});
As you can see I was trying to use defaultValue but with no luck. For new route I want to set default values if we are creating a new service record.
Any help is appreciated.
The argument hash that DS.hasMany only accepts two properties: async and inverse. It doesn't accept a defaultValue property though. (source).
But fear not, Eki Eqbal! I think you can accomplish something similar by using your model's ready() hook.
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
availabilities: DS.hasMany('availability', { async: true }), // is the data loaded async?
ready() { // fires when the model is loaded from server or created locally
if (!!this.get('availabilities')) {
// don't set to defaults if availabilities is not null
return;
}
var saturday = this.store.createRecord('availability', {
day : 'saturday',
enabled : false,
startAt : '',
endAt : ''
});
var sunday = this.store.createRecord('availability', {
day : 'sunday',
enabled : false,
startAt : '',
endAt : ''
});
this.get('availabilities').pushObjects([saturday, sunday]);
}
});
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
I'm trying to create an app using ember.js and ember-data, using the following versions:
DEBUG: Ember : 1.7.0
DEBUG: Ember Data : 1.0.0-beta.9
DEBUG: Handlebars : 1.2.1
DEBUG: jQuery : 2.1.0
I'm using the RESTAdapter to connect to an api I wrote using node.js.
As soon as I load the app I keep getting the following error:
Error while processing route: students undefined is not a function TypeError: undefined is not a function
at http://localhost:9000/scripts/vendor/ember-data.js:12006:34
at tryCatch (http://localhost:9000/scripts/vendor/ember.js:45818:16)
at invokeCallback (http://localhost:9000/scripts/vendor/ember.js:45830:17)
at publish (http://localhost:9000/scripts/vendor/ember.js:45801:11)
at http://localhost:9000/scripts/vendor/ember.js:29069:9
at DeferredActionQueues.invoke (http://localhost:9000/scripts/vendor/ember.js:634:18)
at Object.DeferredActionQueues.flush (http://localhost:9000/scripts/vendor/ember.js:684:15)
at Object.Backburner.end (http://localhost:9000/scripts/vendor/ember.js:147:27)
at Object.Backburner.run (http://localhost:9000/scripts/vendor/ember.js:202:20)
at apply (http://localhost:9000/scripts/vendor/ember.js:18382:27)
Here's the code I'm using (loaded in the same order I pasted it):
app.js
var App = window.App = Ember.Application.create({
LOG_ACTIVE_GENERATION: true,
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: false,
LOG_VIEW_LOOKUPS: true
});
store.js
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'http://localhost:3000',
serializer: DS.RESTSerializer.extend({
primaryKey: function(type) {
return '_id';
},
serializeId: function(id) {
return id.toString();
}
})
});
models/student.js
App.Student = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
nationality: DS.attr('string'),
createdAt: DS.attr('date')
});
routes/app_route.js
App.StudentsRoute = Ember.Route.extend({
model: function() {
return this.store.find('student');
}
});
router.js
App.Router.map(function () {
this.resource('students', {path: '/'});
});
And the following is the response of the API:
{
students: [
{
nationality: "Lorem",
lastName: "Doe",
firstName: "John",
_id: "53f87200f3750319b4791235",
createdAt: "2014-08-23T10:50:40.661Z"
},
{
nationality: "Lorem",
lastName: "Doe",
firstName: "John",
_id: "53f87299f3750319b4791234",
createdAt: "2014-08-23T10:50:40.661Z"
}
]
}
It looks like the store is not loading the data from the API, but the JSON data format looks fine. Any idea of what could be wrong?
Thanks!
So after searching more on Stack Overflow, I've figured out that the serializer has now to be in a separate class than the RESTAdapter, so the working code is the following:
store.js
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'http://localhost:3000'
});
App.ApplicationSerializer = DS.RESTSerializer.extend({
primaryKey: '_id',
serializeId: function(id) {
return id.toString();
}
});
Here's an updated answer for people using ember-cli.
ember g adapter application #=> creates app/adapters/application.js
ember g serializer application #=> creates app/serializers/application.js
In app/adapters/application.js:
import DS from 'ember-data';
export default DS.RestAdapter.extend({
host: 'http://localhost:3000'
});
In app/serializers/application.js:
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
primaryKey: '_id',
serializeId: function(id) {
return id.toString();
}
});
I was getting this error, and it had nothing to do with any of the usual suspects.
In coffeescript, I had started defining a model.
App.Cost = DS.Model.extend
amount: DS.attr 'number'
active: DS.attr 'boolean'
To create a second model, I c/p my first model in and deleted the attributes:
App.Cost = DS.Model.extend
The went back and tried to run a seemingly unrelated model
localhost:3000/products
Which resulted in the error
Error while processing route: products.index
Simply making sure my model was named correctly solved the error:
App.Cost = DS.Model.extend(...)
App.Price = DS.Model.extend(...) <- instead of repeating the Cost model
This was re-produceable, so I thought it might be helpful to others.
I'm having to override a route to do some custom loading of models like this:
App.EventsIndexRoute = Ember.Route.extend
model: (params) ->
origin = LJ.origin().join(',')
location = [LJ.stripQuery(params.loc2), params.loc1].join(',')
h = $.param(origin: origin, location: location)
$.getJSON "#{LJ.CONFIG.api.url}/events?#{h}"
The JSON returned includes sideloaded models, but they aren't being loaded by ember. I'm guessing I need to do something to get them loaded but I don't know what. I'd appreciate any help on this.
Here's an example of the returned JSON.
Update
Here's the model definition:
App.Event = DS.Model.extend
acts: DS.hasMany('App.Act')
ageLimit: DS.attr('string')
centsAdvance: DS.attr('number')
centsDoor: DS.attr('number')
currency: DS.attr('string')
description: DS.attr('string')
endsAt: DS.attr('number')
priceAdvance: DS.attr('string')
priceDoor: DS.attr('string')
repeats: DS.attr('string')
repeatsUntil: DS.attr('string')
startsAt: DS.attr('number')
title: DS.attr('string')
url: DS.attr('string')
venue: DS.belongsTo('App.Venue')
venueSection: DS.attr('string')
IMO this has nothing to do with your route but with your model. Make sure that you declare it like that (they have to be present of course)
App.Event = DS.Model.extend({
venue: DS.belongsTo('App.Venue'),
acts: DS.hasMany('App.Act'),
// go on with the model
});
I found it helpful to include a {{ log event }} into the template to dig into the controller and the model and make sure it really does not get loaded
P.s: You do return the ajax response in the route, do you not?
The link of the example JSON you are returning doesn't work, could you please provide a working sample?
You are going around Ember-data for your ajax call, which would mean you will need to handle deserializing it in a more manual manner. If you want ember to do that for you, you need to call into the store, like:
App.EventsRoute = Ember.Route.extend({
model: function() {
// other code here if necessary
return this.store.find('event', params._id);
}
Also, whenever I am not getting my related objects (venues in your case) loaded into the store, it is because the JSON isn't in the format Ember is expecting, which would look like this:
{
Events: [{ Id: 1, Venues: [1], ... }],
Venues: [{ Id: 1, ... }]
}
NOT LIKE THIS:
{
Events: [{ Id: 1, Venue: { Id: 1, ... }, ... }],
}
Maybe this helps?
When you use findQuery in ember-data, does it also load the model localy ? I can't make the following code to work :
App.MyModel = DS.Model.extend {
name: DS.attr('string')
didLoad: ->
console.log('model loaded')
}
Now when I do something like :
objects = App.store.find(App.MyModel, [{name: "john"},{name: "jack"}])
The didLoad callback is not fired. When this callback is fired ?
To implement the query functionality you have to implement the findQuery method in your adapter. This method takes 4 arguments store, type, query, modelArray. When the server returns the data for the query, you have to invoke the load method on the modelArray to fill it with the query result. This method also loads the data into the store, see an example here: http://jsfiddle.net/pangratz666/5HMGd/.
App.store = DS.Store.create({
revision: 4,
adapter: DS.Adapter.create({
find: Ember.K,
findQuery: function(store, type, query, modelArray) {
// expect server to return this array
modelArray.load([{ id: 1, name: 'John'}, { id: 2, name: 'Jack'}]);
}
})
});
App.MyModel = DS.Model.extend({
name: DS.attr('string'),
didLoad: function() {
console.log('model loaded', this.toJSON());
}
});
// invoke query which loads the 2 models, and didLoad is called
App.store.find(App.MyModel, {});