Как откатить изменения отношений в EmberData

У меня есть две модели отношений между родителями и детьми: тренировка и тренировка:

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.FixtureAdapter: http://jsfiddle.net/tothda/LaXLG/13/

Затем я создал другой, используя DS.RESTAdapter и проблема обнаружилась: http://jsfiddle.net/tothda/qwZc4/5/

В скрипте попробуйте: Edit, Add new и затем Rollback.

Я понял, что в случае RESTAdapter, когда я добавляю новую дочернюю запись в hasMany отношения, родительская запись не станет грязной. Это выглядит нормально, но когда я выполняю откат транзакции, вновь созданная дочерняя запись остается в родительской записи. ManyArray,

Я до сих пор не знаю, как лучше всего справиться с ситуацией.

4 ответа

В Ember Data катастрофически не хватает правильной грязной проверки и отката для отношений hasMany и ownTo. То, как он ведет себя в настоящее время, часто называют ошибкой. Это большая проблема для многих разработчиков, и здесь обсуждается, как решить эту проблему:

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

Пока нет правильного решения, вы можете обойти эту проблему, используя следующий подход.

Сначала вы захотите снова открыть DS.Model и расширить его. Если вы используете глобальные переменные, вы можете просто поместить это (например, DS.Model.reopen({})) где угодно, но если вы используете Ember CLI, лучше всего создать инициализатор (например, ember g modelizer):

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 и ownTo. Мы все еще не полностью обратились к проверке 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, убедитесь, что включили все отношения ownTo, а также включили 'array.[]' И 'array.@ Each.isDirty' для каждого отношения hasMany. Теперь isDirty должен работать как положено.

Это не красиво, но вы можете заставить его откатиться, вручную испачкав родительскую запись:

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

Поздно на вечеринку, но здесь мы идем:

Я создал аддон, который решает эту проблему. Просто позвони rollbackRelationships() и это откатит все ваши отношения (принадлежат & hasMany). Посмотрите на README для большего количества вариантов.

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

@tothda и другие читатели, чтобы следовать. По состоянию на Ember Data : 1.0.0-beta.10+canary.7db210f29a родитель по-прежнему не предназначен для parentTraining.isDirty() значение true, когда ребенок откатывается. Ember Data считает родительскую запись грязной при изменении атрибута, но не при DS.hasMany В массиве есть изменения ( это позволяет save() работать, поэтому вы можете обновлять любые изменения атрибутов родителя на сервере).

Способ обойти это для упомянутого случая, где вы хотите сделать rollback() на только что созданного ребенка, это заменить .rollback() с .deleteRecord() на записи ребенка, которую вы хотите удалить. Затем Ember Data автоматически знает, как удалить его из DS.hasMany массив, и вы можете погладить себя по спине для отката хорошо сделано!

Другие вопросы по тегам