如何在EmberData中回滚关系更改

tot*_*hda 8 relationships ember.js ember-data

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

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

App.Exercise = DS.Model.extend({
  training: DS.belongsTo('App.Training')
})
Run Code Online (Sandbox Code Playgroud)

我希望有一个页面,其中显示了所有相关练习的训练.如果用户按下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({});
  }
})
Run Code Online (Sandbox Code Playgroud)

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

  1. edit:用户按下Edit按钮:创建事务,页面进入"编辑"模式.
  2. cancel:用户按下Cancel按钮:事务回滚并返回"正常"模式.
  3. save:用户按下Save按钮:提交事务并返回"正常"模式.
  4. addExercise:用户按下Add exercise按钮:创建新练习(在同一事务中)并添加到培训中.

除了新创建的记录之外,回滚功能正常工作:如果我按下Edit按钮,添加新练习并按下Cancel按钮,新创建的练习将保留在页面上.

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

更新:

我已经创建了一个jsFiddle来重现问题,但它确实有效.与我在这里使用的应用程序不同DS.FixtureAdapter:http://jsfiddle.net/tothda/LaXLG/13/

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

在小提琴尝试:编辑,添加新,然后回滚.

我想通了,在RESTAdapter的情况下,当我向hasMany关系添加新的子记录时,父记录不会变脏.这看起来很好,但是当我回滚事务时,新创建的子记录保留在父项中ManyArray.

我还是不知道,处理这种情况的最佳方法是什么.

Joh*_*ika 11

在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
};
Run Code Online (Sandbox Code Playgroud)

上面的代码基本上存储了加载或更新时的原始关系,以便以后可以用于回滚和脏检查.

model.rollback()现在应该回滚所有内容,包括hasMany和belongsTo关系.我们仍然没有完全解决'isDirty'检查.为此,我们需要在模型的具体实现中覆盖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;
Run Code Online (Sandbox Code Playgroud)

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

  • 这是一个不错的主意,但现在我的应用程序似乎正在加载所有异步关系(这需要一段时间),而不是等到实际使用?如果我错了,请纠正我. (2认同)

Nic*_*gaz 1

这不太漂亮,但您可以通过手动弄脏父记录来强制它回滚:

parent.send('becomeDirty');
parent.rollback();
parent.get('children.length'); // => 0
Run Code Online (Sandbox Code Playgroud)