Search code examples
ember.jsember-data

Delete associated model with ember-data


I have two models:

App.User = DS.Model.create({
  comments: DS.hasMany('App.Comment')
});

App.Comment = DS.Model.create({
  user: DS.belongsTo('App.User')
});

When a user is deleted, it also will delete all its comments on the backend, so I should delete them from the client-side identity map.

I'm listing all the comments on the system from another place, so after deleting a user it would just crash.

Is there any way to specify this kind of dependency on the association? Thanks!


Solution

  • I use a mixin when I want to implement this behaviour. My models are defined as follows:

    App.Post = DS.Model.extend(App.DeletesDependentRelationships, {
        dependentRelationships: ['comments'],
    
        comments: DS.hasMany('App.Comment'),
        author: DS.belongsTo('App.User')
    });
    
    App.User = DS.Model.extend();
    
    App.Comment = DS.Model.extend({
        post: DS.belongsTo('App.Post')
    });
    

    The mixin itself:

    App.DeletesDependentRelationships = Ember.Mixin.create({
    
        // an array of relationship names to delete
        dependentRelationships: null,
    
        // set to 'delete' or 'unload' depending on whether or not you want
        // to actually send the deletions to the server
        deleteMethod: 'unload', 
    
        deleteRecord: function() {
            var transaction = this.get('store').transaction();
            transaction.add(this);
            this.deleteDependentRelationships(transaction);
            this._super();
        },
    
        deleteDependentRelationships: function(transaction) {
            var self = this;
            var klass = Ember.get(this.constructor.toString());
            var fields = Ember.get(klass, 'fields');
    
            this.get('dependentRelationships').forEach(function(name) {
                var relationshipType = fields.get(name);
                switch(relationshipType) {
                    case 'belongsTo': return self.deleteBelongsToRelationship(name, transaction);
                    case 'hasMany': return self.deleteHasManyRelationship(name, transaction);
                }
            });
        },
    
        deleteBelongsToRelationship: function(name, transaction) {
            var record = this.get(name);
            if (record) this.deleteOrUnloadRecord(record, transaction);
        },
    
        deleteHasManyRelationship: function(key, transaction) {
            var self = this;
    
            // deleting from a RecordArray doesn't play well with forEach, 
            // so convert to a normal array first
            this.get(key).toArray().forEach(function(record) {
                self.deleteOrUnloadRecord(record, transaction);
            });
        },
    
        deleteOrUnloadRecord: function(record, transaction) {
            var deleteMethod = this.get('deleteMethod');
            if (deleteMethod === 'delete') {
                transaction.add(record);
                record.deleteRecord();
            }
            else if (deleteMethod === 'unload') {
                var store = this.get('store');
                store.unloadRecord(record);
            }
        }
    });
    

    Note that you can specify via deleteMethod whether or not you want to send the DELETE requests to your API. If your back-end is configured to delete dependent records automatically, then you will want to use the default.

    Here's a jsfiddle that shows it in action.