How to change return relationship in EmberData

I have two models with parent-child relationships: training and exercises:

App.Training = DS.Model.extend({ exercises: DS.hasMany('App.Exercise') }) App.Exercise = DS.Model.extend({ training: DS.belongsTo('App.Training') }) 

I want to have a page that displays a workout with all the related exercises. If the user clicks the Edit button, the page becomes editable with the ability to add new exercises. I also want to have a Cancel button that discards all changes made.

Here is my controller:

 App.TrainingsShowController = Em.ObjectController.extend({ editing: false, edit: function() { this.set('editing', true); transaction = this.get('store').transaction(); transaction.add(this.get('model')); this.get('model.exercises').forEach(function(x){ transaction.add(x); }); }, cancel: function() { this.set('editing', false); this.get('model.transaction').rollback(); }, save: function() { this.set('editing', false); this.get('model.transaction').commit(); }, addExercise: function() { this.get('model.exercises').createRecord({}); } }) 

There are four event handlers in the controller:

  • Edit : the user clicked the Edit button: a transaction is created, the page is transferred to the "Edit" mode.
  • Cancel : the user clicked the Cancel button: the transaction rolls back and returns to "Normal" mode.
  • save : the user clicked the save button: the transaction is completed and returns to the "Normal" mode.
  • addExercise : The user clicked the Add exercise button: a new exercise was created (in the same transaction) and added to the training.

The rollback function works fine, except for newly created records: if I click the Edit button, add a new exercise and click the Cancel button, the newly created exercise remains on the page.

What is the best way to get rid of a dropped child record?

UPDATE:

I created jsFiddle to reproduce the problem, but it worked. Unlike my application, here I used DS.FixtureAdapter : http://jsfiddle.net/tothda/LaXLG/13/

Then I created another using DS.RESTAdapter and the problem arose: http://jsfiddle.net/tothda/qwZc4/5/

In the script, try: Modify, Add new, and then Rollback.

I realized that in the case of RESTAdapter, when I add a new child entry to the hasMany relation, the parent record will not become dirty. Which seems fine, but when I roll back the transaction, the newly created child entry remains in the parent ManyArray .

I still don’t know how best to deal with the situation.

+8
ember-data relationships
source share
4 answers

The correct dirty check and rollback for hasMany belongs to relationships that are sorely lacking in Ember Data. The way he currently behaves is often reported as a mistake. This is a big pain for many developers, and there is a constant discussion on how to solve this problem here:

https://github.com/emberjs/rfcs/pull/21

Until there is a proper solution, you can solve this problem using the following approach.

First you need to open DS.Model again and expand it. If you use global variables, you can simply put this (e.g. DS.Model.reopen ({})) anywhere, but if you use the Ember CLI, it is best to create an initializer (e.g. ember g initialization model):

 import DS from 'ember-data'; export function initialize(/* container, application */) { DS.Model.reopen({ saveOriginalRelations: function() { this.originalRelations = {}; this.constructor.eachRelationship(function(key, relationship) { if (relationship.kind === 'belongsTo') this.originalRelations[key] = this.get(key); if (relationship.kind === 'hasMany') this.originalRelations[key] = this.get(key).toArray(); }, this); }, onLoad: function() { this.saveOriginalRelations(); }.on('didLoad', 'didCreate', 'didUpdate'), onReloading: function() { if (!this.get('isReloading')) this.saveOriginalRelations(); }.observes('isReloading'), rollback: function() { this._super(); if (!this.originalRelations) return; Ember.keys(this.originalRelations).forEach(function(key) { // careful, as Ember.typeOf for ArrayProxy is 'instance' if (Ember.isArray(this.get(key))) { this.get(key).setObjects(this.originalRelations[key]); this.get(key).filterBy('isDirty').invoke('rollback'); return; } if (Ember.typeOf(this.get(key)) === 'instance') { this.set(key, this.originalRelations[key]); return; } }, this); }, isDeepDirty: function() { if (this._super('isDirty')) return true; if (!this.originalRelations) return false; return Ember.keys(this.originalRelations).any(function(key) { if (Ember.isArray(this.get(key))) { if (this.get(key).anyBy('isDirty')) return true; if (this.get(key).get('length') !== this.originalRelations[key].length) return true; var dirty = false; this.get(key).forEach(function(item, index) { if (item.get('id') !== this.originalRelations[key][index].get('id')) dirty = true; }, this); return dirty; } return this.get(key).get('isDirty') || this.get(key).get('id') !== this.originalRelations[key].get('id'); }, this); } }); }; export default { name: 'model', initialize: initialize }; 

The above code essentially retains the original relationship when downloading or updating, so that later it can be used to roll back and check for dirty ones.

model.rollback () should now roll back everything, including hasMany and belongs to the relationship. However, we have not yet fully considered isDirty. To do this, we need to override isDirty in a specific implementation of the model. The reason we need to do this here and we cannot do it in DS.Model is mainly because DS.Model does not know what property changes need to be tracked. Here is an example of using the Ember CLI. The same approach will be used with global ones, except that you have assigned this class something like App.Book:

 import DS from 'ember-data'; var Book = DS.Model.extend({ publisher: DS.belongsTo('publisher'), authors: DS.hasMany('author'), isDirty: function() { return this.isDeepDirty(); }.property('currentState', 'publisher', 'authors.[]', 'authors.@each.isDirty').readOnly() }); export default Book; 

For dependent arguments to isDirty, be sure to include all belongsTo attributes, and also include 'array. [] 'and' array. @ each.isDirty 'for each hasMany relationship. Now isDirty should work as expected.

+11
source share

This is not very good, but you can force it to be rolled back manually, polluting the parent record:

 parent.send('becomeDirty'); parent.rollback(); parent.get('children.length'); // => 0 
+1
source share

@tothda and other readers. Starting with Ember Data : 1.0.0-beta.10+canary.7db210f29a parent is still not intended to create parentTraining.isDirty() true when the child rolls back. Ember Data considers the parent record dirty when the attribute changes, but not when DS.hasMany has the changes ( this allows you to save () so you can update any changes to the parent attributes on the server).

In this case, for the case when you want to do rollback() for a newly created child, you need to replace .rollback() with .deleteRecord() with the child record that you want to cancel. Then Ember Data automatically knows to remove it from the DS.hasMany array, and you can pat yourself on the back so that the rollback goes well!

0
source share

I created an addon that solves this problem. Just call rollbackAttributes() and it will also roll back your relationship:

https://www.npmjs.com/package/ember-rollback-relationships

0
source share

All Articles