performing rollback on model with hasMany relation - ember.js

I have models defined as :
App.Answer = DS.Model.extend({
name: DS.attr('string'),
layoutName: DS.attr('string')
});
App.Question = DS.Model.extend({
name: DS.attr('string'),
answers: DS.hasMany('answer', {async: true})
});
I have a component that allows for deleting and adding answers to question model. The component comes with apply and cancel button and when the user clicks on cancel, I wanted all the changes(adds/deletes of answers) to be reverted. Currently rollback doesn't do the trick, I event tried model.reload() when using rest adapter and that didn't work for me either. Any idea how I can go about doing a rollback in this situation?
When using the rest adapter, I pretty much fall to the issue pointed here : EmberJS cancel (rollback) object with HasMany
Thanks, Dee
UPDATE :
Since I couldn't perform rollback the intended way, I performed these steps:
1) get all the answers back from the server
2) remove answer association from the question
3) manually add answer association to the question model from data received from server
This seems to be working well BUT sadly I am getting this one error that I cannot shake off.
Here is a jsbin of updated progress: http://jsbin.com/uWUmUgE/2/
Here you can create new answer and then append it to question and do rollback too. BUT, if you follow these steps, you will see the issue I am facing:
1) delete an answer
2) add an answer
3) perform rollback
4) add an answer
It throws this error:
Error: Attempted to handle event didSetProperty on while in state root.deleted.uncommitted. Called with {name: position, oldValue: 1, originalValue: 1, value: 2}.
I will super appreciate any help you can provide.
WORK-AROUND:
One simple workaround was to just hide the answers on delete. I modified the model a bit like:
App.Answer = DS.Model.extend({
name: DS.attr('string'),
layoutName: DS.attr('string'),
markToDelete: DS.attr('boolean', {default: false})
});
And my rollback function had this logic:
answers.forEach(function (answer) {
if(!answer.get('id')){
//newly created answer models that has not persisted in DB
question.get('answers').removeObject(answer);
answer.deleteRecord();
} else {
answer.rollback();
}
});

I'm not sure of your scope but for this relationship (I'm actually rolling back the belongsTo here but I'm curious if this helps in any way)
App.Appointment = DS.Model.extend({
name: DS.attr('string'),
customer: DS.belongsTo('customer', {async: true})
});
App.Customer = DS.Model.extend({
name: DS.attr('string'),
appointments: DS.hasMany('appointment', {async: true})
});
I'm able to rollback both the appointment and it's hasMany customer model like so (from within my route)
App.AppointmentRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
var context = this.get('context');
var dirty =context.get('isDirty');
var dirtyCustomer=context.get('customer.isDirty');
var message = "foo";
if ((dirty || dirtyCustomer) && confirm(message)) {
transition.abort();
}else{
context.get('customer').get('content').rollback();
context.rollback();return true;
}
}
});

Related

Ember Data loading hasMany relation doesn't work in Ember App Kit (EAK)

I'm trying to set up a hasMany relationship between two models and a hasOne (belongsTo in the current version of Ember Data) between the hasMany and hasOne.
I'm working with Ember Data and have a made a RESTful API that works according to Ember's conventions. All the classes can be queried individually.
Bookmark = hasMany -> Termbinding
Termbinding = belongsTo -> Term
Term = belongsTo -> Termbinding
So the goal is to fetch a Bookmark and get the Terms that are attached to it through the Termbinding. I would already be pretty happy to get the Bookmark to Termbinding relation working. I went through all questions posted on here, sadly enough that didn't work.
Router.js
var Router = Ember.Router.extend();
Router.map(function() {
this.resource('bookmarks', { path:'bookmarks'});
this.resource('bookmark', { path:'bookmarks/:bookmark_id' });
this.resource('termbindings', { path:'termbindings' });
this.resource('termbinding', { path:'termbindings/:termbinding_id' });
});
export default Router;
Bookmark.js
var Bookmark = DS.Model.extend({
url: DS.attr('string'),
description: DS.attr('string'),
visits: DS.attr('number'),
termbinding: DS.hasMany('termbinding')
});
export default Bookmark;
Termbinding.js
var Termbinding = DS.Model.extend({
bookmarkId: DS.attr('number'),
termId: DS.attr('number'),
termOrder: DS.attr('number'),
bookmarks: DS.belongsTo('bookmark')
});
export default Termbinding;
I hope someone can help me because this is preventing me from using Ember for my bookmark application. Thanks in advance.
It might be wise to explicitly specify your inverses, i.e.
var Termbinding = DS.Model.extend({
bookmarkId: DS.attr('number'),
termId: DS.attr('number'),
termOrder: DS.attr('number'),
bookmarks: DS.belongsTo('bookmark', { inverse: 'termbinding' })
});
export default Termbinding;
var Bookmark = DS.Model.extend({
url: DS.attr('string'),
description: DS.attr('string'),
visits: DS.attr('number'),
termbinding: DS.hasMany('termbinding', { inverse: 'bookmarks' })
});
export default Bookmark;
Ember Data will try to map inverses for you, however, it is not without faults. It could possibly be that your pluralization of 'bookmarks' on a DS.belongsTo relationship is throwing off its automatic inverse mapping. Typically for belongsTo you would use the singular, 'bookmark'. Conversely, your hasMany would be termbindings: DS.hasMany('termbinding')
Also, if you could show where you're invoking the models that would be greatly appreciated. Typically I find that creating a JSbin at emberjs.jsbin.com helps me isolate the problem and also provides a collaborative space to debug and experiment.

Ember.js get compound data for each item

I'm trying to build the following view with Ember.js:
Users: (x in total)
* User 1: y Posts
* User 2: z Posts
I've created a itemController that is responsible for getting the number of posts of each user.
App.IndexItemController = Ember.ObjectController.extend({
postCount: function() {
var posts = this.get('content').get('posts');
return posts.get('length');
}.property()
});
Full code on jsbin.
Somehow I always get 0 posts for each user, I guess that is because the relationship is not resolved correctly at this.get('content').get('posts'). What would be the right way to do this? Or am I going a completely wrong way?
Bonus question: What can I pass to the property() and should I pass something to it?
You need to set the dependent keys of your computed property, in your case content.posts.length. So the postCount knows when need to be updated.
App.IndexItemController = Ember.ObjectController.extend({
postCount: function() {
var posts = this.get('content').get('posts');
return posts.get('length');
}.property('content.posts.length')
});
Now your computed property is correct, but no data is loaded, this happen because there isn't posts associated with your users, no in the user -> post direction. So you need to add it in the fixture:
App.User.FIXTURES = [
{
id: 1,
name: 'Jon',
nick: 'Jonny',
posts: [1]
},
{
id: 2,
name: 'Foo',
nick: 'Bar',
posts: [2]
}
];
After this an error is raised Uncaught Error: Assertion Failed: You looked up the 'posts' relationship on '<App.User:ember280:1>' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`).
Ember data identified that you have an async relationship, and warns you to setup the property with async: true
App.User = DS.Model.extend({
name: DS.attr('string'),
nick: DS.attr('string'),
posts: DS.hasMany('post', { async: true })
});
This your updated jsbin

How to save changes to Ember.js model without breaking its relationships

I am using the latest version of Ember-data (v1.0.0-beta.2)
I have a problem updating an Ember.JS model. I reduced this case to most simplistic model
App.Post = DS.Model.extend({
subject: DS.attr('string'),
author: DS.belongsTo('user')
});
App.User = DS.Model.extend({
name: DS.attr('string'),
msg: DS.attr('string')
});
I save it using .save() method, and it successfully resolves the promise. I've added some logging to show the problem
App.IndexController = Ember.ObjectController.extend({
actions: {
save: function() {
console.log("Author name before: " + this.get("model").get("author").get("name"));
this.get("model").save().then(function(m) {
console.log("Author object after: " + m.get("author"));
console.log("Author name after: " + m.get("author").get("name"));
});
}
}
});
After saving, the author property becomes just a number 1, not an object.
The JSON returned by the server is always the same. To be able to demonstrate this on JSbin, I had to overwrite DS.RESTAdapter.ajax.
http://jsbin.com/EWUSEkA/3/edit?html,js,output
The question is: How to make Ember sideload or by other means resolve object relationships after saving the object?
This is a known issue with beta 2. The bug report has some suggested workarounds.
https://github.com/emberjs/data/issues/1228
Hopefully it'll be fixed in beta 3.

Creating Child Records in hasMany Relationship with Ember.Js

I haven't found a satisfactory answer through my search, so I figured I'd ask here.
I'm currently using Ember.Js, Ember-Data, and Ember-Firebase-Adapter, and attempting to create a CRUD application which will create a Parent Record, and then subsequent Child Records to said Parent Records.
(note that DS.Firebase.LiveModel is the Firebase adapter equivalent of DS.Model/Ember.Model)
Here are my models, altered to be generic Post/Comment types
App.Post = DS.Firebase.LiveModel.extend({
name: DS.attr('string'),
body: DS.attr('string'),
date: DS.attr('date'),
comments: DS.hasMany('App.Comment', {embedded: 'always'})
});
App.Comment = DS.Firebase.LiveModel.extend({
message: DS.attr('string'),
timestamp: DS.attr('string'),
post: DS.belongsTo('App.Post', {key: "post_id"})
});
(Should my post_id = post?)
And here is my route for creating Comments:
App.PostsCommentRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', App.Comment.find());
}
});
Here's my controller for the PostsCommentRoute:
App.PostsCommentController = Ember.ObjectController.extend({
newMessage: null,
newTimestamp: null,
saveComment: function() {
App.Pbp.createRecord({
message: this.get('newMessage'),
timestamp: this.get('newTimestamp')
})
App.store.commit();
this.set('newMessage', null);
this.set('newTimestamp', null);
}
});
I think I may be missing the serializer? And I've read several things on addArray but the things I tried to plug in did not prove fruitful. Because my comments create fine, however they are not associated to the post in anyway in my JSON.
Is there a way for the created Comments to find the related Post_Id and then associate to said Post when created? So my Post JSON has an array of Comment_Ids which then allows them to be displayed with the post?
Any help, or links with good examples would be much appreciated. I know this is a relatively simple quandary yet I've been stuck on it for some time now :p
What you can try and do is this
post = App.Post.find(1);
post.get('comments').pushObject(App.Comment.createRecord({})); //This will add a new comment to your post and set the post_id as the post id
App.store.commit()
Hope it helps

Ember-data polymorphic associations

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