I am having trouble loading associations through Ext.create. The following works fine when data is retrieved through a server proxy. However when the same data is loaded through Ext.create no associations are created.

The UI component depends on the nested stores and I would like to have default data available if the AJAX call fails or times out and for testing purposes.

I agree that what you're trying to do appears sensible but it doesn't work. My guess is that associations are handled at the reader level and using Ext.create() skips that. It seems very counter-intuitive to me.

It took me quite a while to find a way of doing it that actually worked. Maybe there's a better way but this is what I came up with:

I feel your pain guys. I was just wondering what's going on when associations are not loaded on Ext.create. Working on anything more "complex" than in examples, like a form with grids of associated model data is really frustrating experience. Build JSON by hand for saving primary model AND association models in same atomic AJAX request and then try to figure out how to load back response again...

I've seen comments from the Sencha devs that 4.1 will improve the data model, particularly associations. I don't know the details but I can't be bothered spending hours filing reports on issues until I know what's in 4.1.

IncludeRelationshipJsonWriter.js

This is what we used, that totally worked but it's been a while. Good luck..

Code:

/**
* This class extends the normal Json writer so that related objects are sent down to the server on the CRUD operations.
* The default Sencha writer only sends the data from the top level object being saved. I found two ways to send
* down the relationship/nested data, one is to define a field using the same name as the relationship (don't do
* this), and two to write a writer that does it (this class).
*
* The problem with the first solution is this.. If you define an object like this:
* fields: [
* .. other fields
* {name: 'anotherField', persist: false},
* {name: 'categories'}
* ],
* hasMany: [
* {model: 'helium.model.Category', name: 'categories'}
* ],
*
* In the definition above there is a field that is the same name as the relationship (hasMany). The problem is
* that the attribute 'persist' will not be respected for any of the Category objects.. So if you want to exclude
* attributes from being sent to the server (persisted), the above workaround won't do it.
*
* This class does solve that problem and you should not define a 'field' with the same name as an association.
*
* This is an amalgam of the ideas @:
* http://www.sencha.com/forum/showthread.php?141957-Saving-objects-that-are-linked-hasMany-relation-with-a-single-Store
*/
Ext.define('helium.util.IncludeRelationshipJsonWriter', {
extend: 'Ext.data.writer.Json',
/*
* This function overrides the default implementation of json writer. Any hasMany relationships will be submitted
* as nested objects. When preparing the data, only children which have been newly created, modified or marked for
* deletion will be added. To do this, a depth first bottom -> up recursive technique was used.
*/
getRecordData: function (record) {
//Setup variables
var isPhantom = record.phantom === true,
writeAll = this.getWriteAllFields() || isPhantom,
me = this, i, association, childStore, data = {};
if (writeAll) {
data = me.callParent(arguments);
} else {
var changes, name, field, fields = record.fields, nameProperty = me.nameProperty, key;
changes = record.getChanges();
for (key in changes) {
if (changes.hasOwnProperty(key)) {
field = fields.get(key);
name = field[nameProperty] || field.name;
data[name] = changes[key];
}
}
if (!record.phantom) {
// always include the id for non phantoms
data[record.idProperty] = record.getId();
}
}
//Iterate over all the hasMany associations
for (i = 0; i < record.associations.length; i++) {
association = record.associations.get(i);
if (association.get('type') != "hasmany") {
continue;
}
data[association.get('name')] = [];
childStore = record[association.get('storeName')];
if (childStore) {
//Iterate over all the children in the current association
childStore.each(function (childRecord) {
//Recursively get the record data for children (depth first)
var childData = this.getRecordData.call(this, childRecord);
/*
* If the child was marked dirty or phantom it must be added. If there was data returned that was neither
* dirty or phantom, this means that the depth first recursion has detected that it has a child which is
* either dirty or phantom. For this child to be put into the prepared data, it's parents must be in place whether
* they were modified or not.
*/
if (childRecord.dirty | childRecord.phantom | (childData != null)) {
data[association.get('name')].push(childData);
record.setDirty();
}
}, me);
/*
* Iterate over all the removed records and add them to the preparedData. Set a flag on them to show that
* they are to be deleted
*/
Ext.each(childStore.removed, function (removedChildRecord) {
//Set a flag here to identify removed records
removedChildRecord.set('forDeletion', true);
var removedChildData = this.getRecordData.call(this, removedChildRecord);
data[association.get('name')].push(removedChildData);
record.setDirty();
}, me);
}
}
//Only return data if it was dirty, new or marked for deletion.
if (record.dirty | record.phantom | record.get('forDeletion')) {
return data;
}
}
});

As far as i remember we solved that the way skirtle proposed. We've rolled out that functionality into a model utility class and are only working against a ModelUtils#create function when populating an associative model. That works as expected and is the most time-consuming approach w/o implementing too much extra code that might not work anymore in a subsequent release.

Code:

Ext.define('ExtBbiAC.ModelUtils', {
statics: {
/**
* Creates a new pojo with the help of a JSON reader. This allows to create a pojo with associations
* which is not implemented by the Ext.create() nor Ext.data.Model.create() operation as they do not
* use a reader when populating a model.
* @param model Obligatory.
* @param config Optional.
*/
create: function(model, config) {
var reader = Ext.create('Ext.data.reader.Json', {
model: model
});
var resultSet = reader.read(config);
return resultSet.records[0];
}
}
}