本文介绍了如何回滚EmberData中的关系更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

 应用程序。培训= DS.Model.extend({
练习:DS.hasMany('App.Exercise')
})

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

我想有一个页面,其中显示了所有相关练习的训练。如果用户按编辑按钮,则可以添加新练习,该页面可以编辑。我也想要一个取消按钮,放弃所做的所有更改。



这是我的控制器:

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


save:function(){
this.set('editing',false);
this.get('model.transaction')。commit();
},

addExercise: function(){
this.get('model.exercises')。createRecord({});
}
})

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


  1. 修改:用户按下编辑按钮:创建一个事务,页面进入编辑模式。

  2. 取消:用户按下取消按钮:事务回滚回正常模式。 li>
  3. 保存:用户按下保存按钮:交易被提交并返回正常模式。

  4. addExercise :用户按下添加练习按钮:创建一个新练习(在同一个交易中),并添加到培训中。

回滚功能正常工作,除了新创建记录:如果我按编辑按钮,添加一个新的练习,并按取消按钮,新创建的练习



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



更新:



我创建了一个jsFiddle来重现问题,bu它工作。与我的应用程序不同,我使用 DS.FixtureAdapter



然后我创建了另一个使用 DS.RESTAdapter 并出现问题:



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



我想出来, RESTAdapter当我添加一个新的子记录到一个 hasMany 关系时,父记录不会变脏。这似乎很好,但是当我回滚事务时,新创建的子记录保留在父级的 ManyArray 中。



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

解决方案

对于hasMany和属性关系在Ember Data中非常缺乏。它目前的行为方式通常被报告为一个错误。这对许多开发人员来说是一个很大的困难,而且正在进行如下的讨论:





直到有适当的解决方案,您可以使用以下方法解决这个问题。



首先,您需要重新打开DS.Model并扩展它。如果您使用全局变量,则可以将其放在任何位置(例如DS.Model.reopen({})),但如果使用Ember CLI,则最好创建一个初始化器(例如,ember g initializer模型):

 从ember-data导入DS; 

导出函数初始化(/ *容器,应用程序* /){

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){

//小心,因为ArrayProxy的Ember.typeOf是'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;

返回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.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的类:

 导入DS从ember-data; 

var Book = DS.Model.extend({

发布者:DS.belongsTo('publisher'),

作者:DS.hasMany ('author'),

isDirty:function(){
return this.isDeepDirty();
} .property('currentState','publisher','authors。 []','authors。@ each.isDirty')。readOnly()

});

导出默认书;

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


I have two models with parent-child relationship: training and exercise:

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

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

I want to have a page where a training with all its related exercises is displayed. If the user presses the Edit button, the page becomes editable with the possibility of adding new exercises. I also want to have a Cancel button which discards all the changes made.

Here is my controller:

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

There are four event handlers in the controller:

  1. edit: The user pressed the Edit button: a transaction is created, the page is put into "Editing" mode.
  2. cancel: The user pressed the Cancel button: transaction is rolled back and back to "Normal" mode.
  3. save: The user pressed the Save button: transaction is commited and back to "Normal" mode.
  4. addExercise: The user pressed the Add exercise button: a new exercise is created (in the same transaction) and added to the trainings.

The rollback functionality works fine except for newly created records: if I push the Edit button, add a new exercise and push the Cancel button, the newly created exercise stays on the page.

What is the best way to get rid of the discarded child record?

UPDATE:

I've created a jsFiddle to reproduce problem, but it worked. Unlike my application here I used DS.FixtureAdapter: http://jsfiddle.net/tothda/LaXLG/13/

Then I've created an other one using DS.RESTAdapter and the problem showed up: http://jsfiddle.net/tothda/qwZc4/5/

In the fiddle try: Edit, Add new and then Rollback.

I figured it out, that in case of the RESTAdapter when I add a new child record to a hasMany relationship, the parent record won't become dirty. Which seems fine, but when I rollback the transaction, the newly created child record stays in the parent's ManyArray.

I still don't know, what's the best way to handle the situation.

解决方案

A proper dirty check and rollback for hasMany and belongsTo relationships are sorely lacking in Ember Data. The way it currently behaves is often reported as a bug. This is a big pain point for a lot of developers and there is an ongoing discussion on how to resolve this here:

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

Until there's a proper solution in place, you can workaround this problem by using the following approach.

First, you'll want to reopen DS.Model and extend it. If you're using globals, you can can just put this (e.g. DS.Model.reopen({})) anywhere, but if you're using Ember CLI, it's best to create an initializer (e.g. ember g initializer 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 code above essentially stores the original relationships on load or update so that it can later be used for rollback and dirty checking.

model.rollback() should now roll back everything, including hasMany and belongsTo relationships. We still haven't fully addressed the 'isDirty' check though. To do that, we need to override isDirty in the concrete implementation of a model. The reason why we need to do it here and we can't do it generically in DS.Model is because DS.Model doesn't know what property changes to watch for. Here's an example using Ember CLI. The same approach would be used with globals, except that you'd assign this class to 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 the dependent arguments of isDirty, make sure to include all belongsTo relationships and also include 'array.[]' and 'array.@each.isDirty' for every hasMany relationship. Now isDirty should work as expected.

这篇关于如何回滚EmberData中的关系更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-28 17:48