【问题标题】:How to rollback relationship changes in EmberData如何回滚 EmberData 中的关系更改
【发布时间】:2013-02-04 10:45:07
【问题描述】:

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

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 按钮:创建了一个新练习(在同一事务中)并添加到训练中。

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

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

更新:

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

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

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

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

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

【问题讨论】:

    标签: ember.js ember-data relationships


    【解决方案1】:

    在 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”检查。为此,我们需要在模型的具体实现中重写 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 关系,并为每个 hasMany 关系包含 'array.[]' 和 'array.@each.isDirty'。现在 isDirty 应该可以正常工作了。

    【讨论】:

    • 这是个好主意,但现在我的应用程序似乎正在立即加载所有异步关系(这需要一段时间),而不是等到实际使用?如果我错了,请纠正我。
    • @justastefan,很好的观察。 saveOriginalRelations 可能正在加载所有异步关系。如果你有异步关系,那么实现这种技术会比较棘手。您只需要在加载后存储原始关系,并忽略回滚任何尚未加载的关系。
    • 升级到 ember-data 1.13.7 时,我 tried to update 也有这种解决方法,但没有成功。你能帮忙吗?
    • Here 是 Ember 2.1 的上述更新。 @masciugo 的诀窍是使用findRecord 来获取模型及其相关记录。我的用例也有可能删除的相关记录,取消删除它们的修复方法是在将原始值设置回模型之前回滚
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      @tothda 和其他读者关注。从Ember Data : 1.0.0-beta.10+canary.7db210f29a 开始,父级仍未设计为在子级回滚时将parentTraining.isDirty() 的值设为true。 Ember Data 确实在属性更改时将父记录视为 dirty,但在 DS.hasMany 数组发生更改时this allows save() 起作用,所以您可以在服务器上更新对父级属性的任何更改)。

      对于提到的情况,解决此问题的方法是,您想对新创建的子项执行 rollback(),将您要丢弃的子项记录上的 .rollback() 替换为 .deleteRecord()。然后 Ember Data 会自动知道将其从 DS.hasMany 数组中删除,然后您可以拍拍自己的背部以完成回滚!

      【讨论】:

        【解决方案4】:

        派对迟到了,但我们走了:

        我创建了一个插件来解决这个问题。 只需致电rollbackRelationships(),它就会回滚您的所有关系(belongsTo & hasMany)。查看 README 以获得更多选项。

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

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-09-29
        相关资源
        最近更新 更多