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.