I am trying to migrated my app to using Ember-Data as it's persistence mechanism. One thing that strikes me is that I'm not sure if it's still possible to use an arrayProxy for aggregate properties of a hasMany association. In my previous iteration I didn't have any explicit associations, just controllers tied together by specific properties. Now I'd like to take advantage of the association functionality in ember-data, but I am getting errors when I trie to bind the content of my array proxy to the "children" property of the DS.Model. My code is below and there is a jsfiddle here: http://jsfiddle.net/sohara/7p6gb/22/
The error I get is:
Uncaught TypeError: Object App.recipeController.content.ingredients has no method 'addArrayObserver'
I would like to be able to retain a controller layer, even if the data associations are controlleed at the model level. It'd also (ideally) like the child objects to be embedded in the json representation of the parent object in order to avoid multiple server requests.
window.App = Ember.Application.create();
App.store = DS.Store.create({
revision: 3,
adapter: DS.fixtureAdapter
});
App.Ingredient = DS.Model.extend({
name: DS.attr('string'),
price: DS.attr('string')
});
App.Recipe = DS.Model.extend({
name: DS.attr('string'),
ingredients: DS.hasMany('App.Ingredient', {embedded: true} )
});
App.Recipe.FIXTURES = [
{id: 1, name: 'Pizza', ingredients: [{id: 1, name: 'tomato sauce', price: 2, recipeId: 1}]}
];
App.recipeController = Ember.Object.create({
content: App.store.find(App.Recipe, 1)
});
App.ingredientsController = Ember.ArrayProxy.create({
content: 'App.recipeController.content.ingredients',
totalWeigth: function() {
var price = 0;
items = this.get('content');
items.forEach(function(item) {
weight += price;
});
}.property()
});
In App.ingredientsController you need to have contentBinding: 'App.recipeController.content.ingredients', instead of content: ...
Related
I'm trying to convert Ember-Data Model instances to an array with the goal of loading it into an Handsontable (http://handsontable.com). The toArray() method does not seems to convert the attributes of the StatVals Model to an Array but to create an Array of StatVals objects. How to properly construct an Array from a Ember-Data Model Attributes?
Further, I want the new array to contain the time and element and not the other attributes of the statVal model. How should I approach this?
The View that construct the Handsontable:
App.chapterView = Ember.View.extend({
tagName: 'div',
classNames: ['dataTable'],
insertTable: function(){
var divElement = jQuery('.dataTable');
var data = this.get('controller.model.statVals').toArray();
divElement.handsontable({ data: data });
}.on('didInsertElement')
});
Model:
App.Chapter = DS.Model.extend({
name: DS.attr('string'),
createdDate: DS.attr('date'),
statVals: DS.hasMany('statVal', { inverse: 'chapter', async: true}),
user: DS.belongsTo('user', { inverse: 'chapter', async: true} )
});
App.StatVal = DS.Model.extend({
time: DS.attr('date'),
Element: DS.attr('number'),
Chapter: DS.belongsTo('chapter'),
user: DS.belongsTo('user', { inverse: 'statVals', async: true} )
});
I found the below related questions but I'm not convince that they present the best approach to my situation:
ember-data as data for d3
What is the Ember way of converting retrieved Ember Data records into plain objects?
Simply removing the statVals in var data = this.get('controller.model.statVals').toArray(); enabled me to feed the Handsontable table with data from Ember-Data. The complete View code is:
App.chapterView = Ember.View.extend({
tagName: 'div',
classNames: ['dataTable'],
insertTable: function(){
var divElement = jQuery('.dataTable');
var data = this.get('controller.model').toArray();
divElement.handsontable({ data: data });
}.on('didInsertElement')
});
Below is the code I am using to get data from web API. But every time I try to retrieve data I get same error: Unable to set property 'store' of undefined or null reference in ember.js.
/// <reference path="Lib/ember.js" />
/// <reference path="Lib/ember-data.js" />
var App = Ember.Application.create();
Ember.onerror = function(e) {
alert(e);
};
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'api'
});
App.store = DS.Store.create({
adapter: App.ApplicationAdapter
});
App.Product = DS.Model.extend({
ID: DS.attr("int"),
Name: DS.attr('string'),
Category: DS.attr('string'),
});
App.ApplicationRoute = Ember.Route.extend({
model: function() {
{{debugger}}
var store1 = this.get("store")
var k = store1.find('product', 1)
return k;
}
});
Your problem is with the returned json from the server. You need to return the following object:
{
product: {
// key value
}
}
If you want to use the DS.RESTAdapter defaults, you can just return the data in that format:
{
product: {
id: 1,
name: 'some name',
category: 'some category'
}
}
And change your model mapping to:
App.Product = DS.Model.extend({
name: DS.attr('string'),
category: DS.attr('string'),
});
If you want to use the capitalized properties like Name, Category. You will need to override some methods of DS.RESTAdapter. If your endpoing doesn't match this format.
Other error is that doesn't exist a DS.attr('int') just DS.attr('number'). But you can remove the id mapping, since is created by default.
This is a jsfiddle with this working http://jsfiddle.net/marciojunior/W5LEH/
Ensure that you are using last versions of Ember.js and Ember-Data.
This is how you define a store for you application:
App.Store = DS.Store.extend({
adapter: App.ApplicationAdapter
});
Note the capital S in Store and extend instead of create.
See Ember-Data Guide
I would get the records of my field children. Here the code:
App.User = DS.Model.extend({
name: DS.attr('string'),
online: DS.attr('boolean')
});
App.List = DS.Model.extend({
name: DS.attr('string'),
children: DS.hasMany('App.User'),
online: function() {
var users = this.get("children");
return users.reduce(0, function(previousValue, user){ // no record founds
return previousValue + user.get("online");
});
}.property("children.#each.online")
});
But App.List.find(1).get('online') returns no record. (For some reason I cannot specify that App.List.children contains many records, of type App.Users, as embedded records).
Here is the fiddle: JSBIN and it's output
How I can solve my issue?
Define the embedded Model on your Adapter map:
App.List = DS.Model.extend({
name: DS.attr('string'),
users: DS.hasMany('App.User'), //use "users" as the property name to mantain ember's naming conventions
...
});
App.Adapter = DS.RESTAdapter.extend();
App.Adapter.map('App.List', {
users: {embedded: 'always'} //you can use `always` or `load` which is lazy loading.
});
App.Store = DS.Store.extend({
revision: 12,
adapter: App.Adapter.create()
});
Hope it helps
Has anybody come up with an answer for polymorphic associations and ember-data?
We would need some way of being able to query the type at the other end of the relationship from what I can tell.
Anybody any thoughts on this?
With the latest ember-data build you can now use polymorphic associations:
You need to configure your Models to make it polymorphic:
/* polymorphic hasMany */
App.User = DS.Model.extend({
messages: DS.hasMany(App.Message, {polymorphic: true})
});
App.Message = DS.Model.extend({
created_at: DS.attr('date'),
user: DS.belongsTo(App.User)
});
App.Post = App.Message.extend({
title: DS.attr('string')
});
/* polymorphic belongsTo */
App.Comment = App.Message.extend({
body: DS.attr('string'),
message: DS.belongsTo(App.Message, {polymorphic: true})
});
You also need to configure alias properties on your RESTAdapter
DS.RESTAdapter.configure('App.Post' {
alias: 'post'
});
DS.RESTAdapter.configure('App.Comment' {
alias: 'comment'
});
The result expected from your server should be like this:
{
user: {
id: 3,
// For a polymorphic hasMany
messages: [
{id: 1, type: "post"},
{id: 1, type: "comment"}
]
},
comment: {
id: 1,
// For a polymorphic belongsTo
message_id: 1,
message_type: "post"
}
}
More information in this github thread
So I have something. It's not finished, or entirely clean, but it works. Basically, I use a mixin to bypass the Ember associations entirely. I'm sure that this could be rolled into the adapter or the store, but for now this works.
Polymorphic models come through the the JSON with an itemId and itemType:
App.Follow = DS.Model.extend
user: DS.belongsTo('App.User')
itemId: DS.attr("number")
itemType: DS.attr("string")
I add a mixin to the models that are associated with it :
App.Hashtag = DS.Model.extend App.Polymorphicable,
follows:(->
name: DS.attr("string")
#polymorphicFilter(App.Follow, "Hashtag")
).property('changeCount') #changeCount gives us something to bind to
followers: (->
#get('follows').map((item)->item.get('user'))
).property('follows')
The mixin implements three methods, one that updates the changeCount, one that returns the model's type and the polymorphicFilter method that filters a model by itemType and id:
App.Polymorphicable = Ember.Mixin.create
changeCount: 1
polymorphicFilter: (model, itemType)->
App.store.filter model,
(data) =>
if data.get('itemId')
#get('id') is data.get('itemId').toString() and data.get('itemType') is itemType
itemType:()->
#constructor.toString().split('.')[1]
updatePolymorphicRelationships:()->
#incrementProperty('changeCount')
The controller layer is protected from all this jankyness, except for having to call updatePolymorphicRelationship to make sure the bindings fire:
App.HashtagController = Ember.ObjectController.extend
follow:()->
App.Follow.createRecord({
user: #get('currentUserController.content')
itemId: #get('id')
itemType: #get('content').itemType()
})
#this provides a way to bind and update. Could be refactored into a didSave()
#callback on the polymorphic model.
#get('content').updatePolymorphicRelationships()
App.store.commit()
That's what I have so far. I'm trying to keep things in the model layer as it's just one step removed from the adapter layer. If it looks like Ember Data is not going to look at polymorphics at all in future, then it would make sense to pull this all up to a higher level, but for now, this works and leaves my controllers (relatively) clean.
Polymorphic associations are now supported in ember data
https://github.com/emberjs/data/commit/e4f7c3707217c6ccc0453deee9ecb34bd65c28b9
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, {});