如何在EmberData中回滚关系更改

时间:2013-02-04 10:45:08

标签: ember.js ember-data relationships

我有两个有亲子关系的模型:训练和锻炼:

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

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

我希望有一个页面,其中显示了所有相关练习的训练。如果用户按下Edit按钮,页面将变为可编辑状态,并可添加新练习。我还想要一个Cancel按钮,它会丢弃所做的所有更改。

这是我的控制器:

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({});
  }
})

控制器中有四个事件处理程序:

  1. edit:用户按下Edit按钮:创建了一个事务,页面进入“编辑”模式。
  2. cancel:用户按下Cancel按钮:事务回滚并返回“正常”模式。
  3. save:用户按下了Save按钮:提交了事务并返回“正常”模式。
  4. addExercise:用户按下了Add exercise按钮:创建了一个新练习(在同一个交易中)并添加到培训中。
  5. 除了新创建的记录之外,回滚功能正常工作:如果我按下Edit按钮,添加新练习并按下Cancel按钮,新创建的练习将保留在页面上。

    摆脱丢弃的儿童记录的最佳方法是什么?

    更新

    我创建了一个jsFiddle来重现问题,但它确实有效。与我的申请不同,我使用了DS.FixtureAdapterhttp://jsfiddle.net/tothda/LaXLG/13/

    然后我使用DS.RESTAdapter创建了另一个,问题出现了:http://jsfiddle.net/tothda/qwZc4/5/

    在小提琴中尝试:编辑,添加新内容然后再回滚。

    我想通了,在RESTAdapter的情况下,当我将新的子记录添加到hasMany关系时,父记录不会变脏。这似乎很好,但当我回滚事务时,新创建的子记录保留在父ManyArray中。

    我仍然不知道,处理这种情况的最佳方法是什么。

4 个答案:

答案 0 :(得分:12)

在Ember Data中非常缺乏hasMany和belongsTo关系的正确脏检查和回滚。它目前的行为方式通常被报告为错误。对于很多开发人员来说,这是一个很大的痛点,目前正在讨论如何解决这个问题:

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

在有适当的解决方案之前,您可以使用以下方法解决此问题。

首先,您要重新打开DS.Model并对其进行扩展。如果你正在使用全局变量,你可以把它(例如DS.Model.reopen({}))放在任何地方,但是如果你使用Ember CLI,最好创建一个初始化器(例如,ember g初始化模型):

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
};

上面的代码实际上是在加载或更新时存储原始关系,以便以后可以用于回滚和脏检查。

model.rollback()现在应该回滚所有内容,包括hasMany和belongsTo关系。我们仍然没有完全解决“肮脏的”问题。虽然检查。为此,我们需要在模型的具体实现中覆盖isDirty。我们之所以需要在此处执行此操作并且我们无法在DS.Model中执行此操作,是因为DS.Model不知道要监视的属性更改。以下是使用Ember CLI的示例。除了你将这个类分配给像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;

对于isDirty的依赖参数,请确保包含所有belongsTo关系,并且还包括' array。[]'和'数组。@ each.isDirty'对于每个hasMany关系。现在isDirty应该按预期工作。

答案 1 :(得分:1)

这不是很好但你可以通过手动弄脏父记录来强制它回滚:

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

答案 2 :(得分:0)

@tothda和其他读者关注。截至Ember Data : 1.0.0-beta.10+canary.7db210f29a时,父级仍未设计为在回滚子项时使parentTraining.isDirty()值为 true 。当一个属性发生变化时,Ember Data 认为父记录为dirty,而当DS.hasMany数组发生变化时, <{3>}工作,所以你可以更新服务器上父项属性的任何更改。

对于上述案例,您要对新创建的子项执行rollback()的方法是将.rollback()替换为子记录上的.deleteRecord()想要丢弃。然后,Ember Data会自动知道将它从DS.hasMany数组中删除,然后您可以拍拍自己的背面以完成回滚!

答案 3 :(得分:0)

晚会,但我们走了:

我创建了一个解决此问题的插件。 只需致电rollbackRelationships(),它就会回滚您的所有关系( belongsTo &amp; hasMany )。查看自述文件以获取更多选项。

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