ExtJS 4.1 - Возврат связанных данных в Model.Save() Response

Мне любопытно, почему запись, содержащаяся в результирующем наборе ответа Model.save(), неправильно возвращает обновленные связанные данные, несмотря на то, что обновленные данные содержатся в ответе сервера...

Пример модели и определение хранилища:

Ext.define("App.model.test.Parent",{
    extend: 'Ext.data.Model',
    requires: ['App.model.test.Child'],
    fields: [
            {name: 'id', type: 'int' },
            {name: 'name', type: 'string'},
            {name: 'kids', type: 'auto', defaultValue: []}
    ],
    idProperty: 'id',

    hasMany: [{
            foreignKey: 'parent_id',
            model: 'App.model.test.Child', 
            associationKey: 'kids',
            name: 'getKids'   
    }],

    proxy: {
        type: 'ajax',
        api : {
            create: '/service/test/create/format/json',
            read : '/service/test/read/format/json',
            update : '/service/test/update/format/json'
        },

        reader: {
            idProperty      : 'id',
            type            : 'json',
            root            : 'data',        
            successProperty : 'success',       
            messageProperty : 'message'
        },

        writer: {
            type            : 'json',
            writeAllFields  : true
        }
    }
});

Ext.define("App.model.test.Child",{
    extend: 'Ext.data.Model',
    fields: [
        {name: 'id', type: 'int' },
        {name: 'name', type: 'string'},
        {name: 'parent_id', type: 'int'}
    ]
});

Ext.define("App.store.test.Simpson",{
    storeId: 'TheSimpsons',
    extend: 'Ext.data.Store',
    model : 'App.model.test.Parent',
    autoLoad: true,
    autoSync: false
});

Ответ сервера приложений на запрос прокси READ с одной моделью и связанными с ней данными. Это все работает hunky dory!

Ответ сервера на запрос READ

{
"data":{
    "id":1,
    "name":"Homer Simpson",
    "children":{
        "1":{
            "id":1,
            "name":"Bart Simpson"
        },
        "2":{
            "id":2,
            "name":"Lisa Simpson"
        },
        "3":{
            "id":3,
            "name":"Maggie Simpson"
        }
    }
},
"success":true,
"message":null
}

До сих пор все работает по плану...

store = Ext.create("App.store.test.Simpson");
homer = store.getById(1);
kids  = homer.getKids().getRange();
console.log("The Simpson Kids", kids);  // [>constructor, >constructor, >constructor]

НЕЗАВИСИМОЕ ПОВЕДЕНИЕ НАЧИНАЕТСЯ С ЗАДАЧАМИ СОХРАНЕНИЯ И ОБНОВЛЕНИЯ

Вот мой тестовый ответ для запроса UPDATE...

/** Server UPDATE Response */
{
"data":{
    "id":1,
    "name":"SAVED Homer Simpson",
    "kids":[{
        "id":1,
        "name":"SAVED Bart Simpson",
        "parent_id":1
    },{
        "id":2,
        "name":"SAVED Lisa Simpson",
        "parent_id":1
    },{
        "id":3,
        "name":"SAVED Maggie Simpson",
        "parent_id":1
    }]
},
"success":true,
"message":null
}


/** Will call proxy UPDATE, response is above */
homer.save({
    success: function(rec, op){
        var savedRec = op.getRecords().pop(),
            kidNames = '';
        console.log(savedRec.get('name')); // SAVED Homer Simpson = CORRECT!
        Ext.each(savedRec.getKids().getRange(), function(kid){
            kidNames += kid.get('name') + ", ";
        });
        console.log(kids); 
        //Outputs: Bart Simpson, Lisa Simpson, Maggie Simpson = WRONG!!
    }
})

Я замечаю, что если я проверю запись, возвращенную сервером, созданный Магазин Ассоциации (т.е. getKidsStore), содержащиеся записи являются исходными записями, т.е. у них нет имени "SAVED". Однако свойство kids возвращенной записи действительно содержит правильные данные.

Если я правильно понял проблему, это значит, что Ext.data.reader.Reader неправильно обновляет связанное хранилище с соответствующими данными, содержащимися в ответе .save(). Если это так, на мой взгляд, это очень неинтуитивно, так как я бы ожидал такого же поведения, как читатель, который обрабатывает запрос store.load() и начинает заполнять созданные хранилища ассоциаций.

Может ли кто-нибудь указать мне в правильном направлении в достижении моего поведения?

Отказ от ответственности: Здесь задан один и тот же вопрос: ExtJs 4 - Загрузите вложенные данные при сохранении записи, но без ответа. Я чувствую, что мой вопрос будет немного более тщательным.

EDIT: Я опубликовал этот вопрос на форумах Sencha: http://www.sencha.com/forum/showthread.php?270336-Associated-Data-in-Model.save()-Response

РЕДАКТИРОВАТЬ (8/23/13): Я повторно написал это сообщение с примером COMPLETE, а также с дополнительными выводами...

Ответ 1

Я нашел проблему, или, скорее, путаница лежит в методе getRecords() Ext.data.Operation. Этот метод возвращает "операция, первоначально настроенная запись будет возвращена, хотя прокси-сервер может изменить данные этих записей в какой-то момент после инициализации операции". согласно документации.

Это довольно запутанная ИМО, поскольку возвращаемая запись действительно обновляется, однако созданный магазин ассоциаций и, следовательно, связанные данные не являются! Это то, что привело к моей путанице, показалось, что запись содержала обновленные данные с сервера приложений, но это было не так.

Чтобы облегчить мой простой ум при получении обновленных данных FULLY из ответа, я добавил метод в класс Ext.data.Operation... Я просто написал этот метод и не получил испытал его больше, чем обеспечение функциональности, которую я искал, поэтому используйте на свой страх и риск!

Пожалуйста, имейте в виду, что я не вызываю store.sync(), скорее я создаю экземпляр модели и вызываю метод model.save(), поэтому мой результирующий набор обычно содержит только одну запись...

Ext.override(Ext.data.Operation,{
    getSavedRecord: function(){
        var me = this, // operation
            resultSet = me.getResultSet();

        if(resultSet.records){
            return resultSet.records[0];
        }else{
            throw "[Ext.data.Operation] EXCEPTION: resultSet contains no records!";
        }

    }
});

Теперь я могу достичь функциональности, которой я был после...

// Get the unsaved data
store = Ext.create('App.store.test.Simpson');
homer = store.getById(1);
unsavedChildren = '';

Ext.each(homer.getKids().getRange(), function(kid){
    unsavedChildren += kid.get('name') + ",";
});

console.log(unsavedChildren); // Bart Simpson, Lisa Simpson, Maggie Simpson

// Invokes the UPDATE Method on the proxy
// See original post for server response
home.save({
    success: function(rec, op){
        var savedRecord = op.getSavedRecord(), // the magic! /sarcasm
            savedKids   = '';

        Ext.each(savedRecord.getKids().getRange(), function(kid){
            savedKids += kid.get('name') + ',';
        });

        console.log("Saved Children", savedKids);

        /** Output is now Correct!!
            SAVED Bart Simpson, SAVED Lisa Simpson, SAVED Maggie Simpson
          */
    }
});

Редактировать 12/10/13 Я также добавил метод Ext.data.Model, который я назвал updateTo, который обрабатывает обновление записи к предоставленной записи, которая также обрабатывает ассоциации. Я использую это в сочетании с вышеупомянутым методом getSavedRecord. Обратите внимание, что это не обрабатывает ассоциации belongsTo, поскольку я не использую их в своем приложении, но эту функциональность можно будет легко добавить.

/**
 * Provides a means to update to the provided model, including any associated data
 * @param {Ext.data.Model} model The model instance to update to. Must have the same modelName as the current model
 * @return {Ext.data.Model} The updated model
 */
updateTo: function(model){
    var me = this,
    that = model,
    associations = me.associations.getRange();

    if(me.modelName !== that.modelName)
    throw TypeError("updateTo requires a model of the same type as the current instance ("+ me.modelName +"). " + that.modelName + " provided.");

    // First just update the model fields and values
    me.set(that.getData());

    // Now update associations
    Ext.each(associations, function(assoc){
    switch(assoc.type){
        /**
         * hasOne associations exist on the current model (me) as an instance of the associated model.
         * This instance, and therefore the association, can be updated by retrieving the instance and
         * invoking the "set" method, feeding it the updated data from the provided model.
         */
        case "hasOne":
            var instanceName  = assoc.instanceName,
                currentInstance = me[instanceName],
                updatedInstance = that[instanceName];

             // Update the current model hasOne instance with data from the provided model
             currentInstance.set(updatedInstance.getData());

            break;

        /** 
         * hasMany associations operate from a store, so we need to retrieve the updated association
         * data from the provided model (that) and feed it into the current model (me) assocStore
         */
        case "hasMany":
            var assocStore = me[assoc.storeName],
                getter     = assoc.name,
                newData    = that[getter]().getRange();

            // Update the current model hasMany association store with data from the provided model hasMany store
            assocStore.loadData(newData);
            break;

        // If for some reason a bogus association type comes through, throw a type error
        // At this time I have no belongsTo associations in my application, so this TypeError
        // may one day appear if I decide to implement them.
        default:
            throw TypeError("updateTo does not know how to handle association type: " + assoc.type);
            break;
    }
    });

    // Commit these changes
    me.commit();

    return me;
}

Итак, в основном я делаю что-то вроде этого (это теоретически было бы в контроллере Order)

doSaveOrder: function(order){
    var me = this,                       // order controller 
        orderStore = me.getOrderStore(); // magic method

    // Save request
    order.save({
        scope: me,
        success: function(responseRecord, operation){ 
            // note: responseRecord does not have updated associations, as per post
            var serverRecord = operation.getSavedRecord(),
                storeRecord  = orderStore.getById(order.getId());

            switch(operation.action){
                case 'create':
                    // Add the new record to the client store
                    orderStore.add(serverRecord);
                break;

                case 'update':
                    // Update existing record, AND associations, included in server response
                    storeRecord.updateTo(serverRecord);
                break;
            }
        }
    });
}

Я надеюсь, что это поможет кому-то, кто был смущен, как и я!

Ответ 2

Полностью согласен с вами. Действительно странное поведение. Он должен обновить хранилище ассоциаций в записи. Вот как я обошел эту проблему (в основном просто запускаю ответ через читателя!):

 success: function(record, operation) {
     var newRecord= me.getMyModel().getProxy().reader.read(operation.response).records[0];
 }

Ответ 3

Если ваше поле ID имеет значение, то ExtJS всегда будет вызывать обновление. Если вы не записываете какое-либо значение в свое поле id или не устанавливаете его в null, он должен вызвать create. Думаю, вы пытаетесь вызвать save с существующей записью, поэтому она всегда будет вызывать обновление. Это желаемое поведение.