В 4-й версии появилось многообещающий класс Association, от соблазна использовать который устоять практически невозможно. Проблема однако в том, что его документация мало соответствует тому, как он работает на самом деле. Причем несоответствие оказывается и на уровне того, как авторы хотели, чтобы все было и того, как у них получилось.
Во-первых, в одном классе объединены фактически две разных функциональности. Одна – это трансляция в Ext.data.Model сложных типов данных с вложенными объектами, а другая – это работа с реляционными моделями. При том, что используется одно и тоже API входные параметры и возвращаемые результаты оказываются разными и принимают разные значения. Чтобы убедиться/ужаснуться с чем приходится иметь дело достаточно посмотреть на этот фрагмент:

Во-вторых, товарищи разработчики где-то в самом начале запутались, где работает Ext.loader, который умеет находить классы по имени, а где другие менеджеры классов, которые требую полный путь.
Итак, вначале вариант со сложными данными. Пусть имеется вот такая JSON-структура:
[{
id: 1,
site: 'www.armour.up',
component: {
id: 2,
name: 'theme'}
},{
id: 3,
site: 'www.armour.up',
component_id: 4
component: {
id: 4,
name: 'plugins'
}
}]
Т.е. есть два объекта с описанием сайтов, внутри которого объекты-описания компонент которые возвращаются одним запросом. В этом случая модель, которая корректно обработает эти данные может иметь вид:
Ext.define('D.model.Deploy', {
extend: 'Ext.data.Model',
alias: 'model.deploy',
fields:[{
name:'id', type: 'int'},{
name:'site', type: 'string'},{
}],
associations: [{
type: 'belongsTo',
model: 'D.model.Component',
associatedName: 'Component',
instanceName: 'component'
}],
Самое сложное и путанное здесь – это опции assotiations.
model должна быть полным путем, так как определяет имя класса-модели для дочерней записи;
associatedName будет использоваться для автоматического именования производных полей:
name : associatedName,
foreignKey : associatedName.toLowerCase() + "_id",
instanceName: associatedName + 'BelongsToInstance',
associationKey: associatedName.toLowerCase()
getterName = me.getterName || 'get' + associatedName,
setterName = me.setterName || 'set' + associatedName;
instanceName – это имя поля родительской модели в котором сохранится созданная дочерняя модель. Можно его оставить в ComponentBelongsToInstance, но я предпочел более логичное component
Дальше все просто, при чтении структуры данных моделью Deploy для дочерних элементов component будут создаваться соответсвующие модели D.model.Component (описание опущено за очевидностью), которые будут доступны либо по record.component(record.ComponentBelongsToInstance) либо record.getComponent().
Все меняется, если предполагается что, что структура данных имеет вид типичный для выборки из реляционной базы:
[{
id: 1,
site: 'www.armour.up',
component_id: 2
},{
id: 3,
site: 'www.armour.up',
component_id: 4
}]
а имена компонент нужно получать отдельным запросом. Тогда описание ассоциации радикально меняется:
associations: [{
type: 'belongsTo',
model: 'D.model.Component',
associatedName: 'D.model.Component',
getterName: 'getComponent',
name: 'component',
foreignKey: 'component_id',
instanceName: 'component'
}]
Неприятности начинаются из-за того, что в одном месте в коде associatedName используется для определения класса модели и поэтому должно быть полными путем. Из-за этого имя геттера, например, становится очень кучерявым: getD.model.Component, что явно не то, что кто нибудь может хотеть. Чтобы избежать таких проблем приходится явно именовать все остальные опции. Ну и указать имя поля в модели, которая является ключом для поиска связанной foreignKey.
Кроме того, что меняется описание ассоциации, совершенно иначе себя начинает вести getComponent. Эта функция больше не возвращает никакого значения, а напротив принимает параметром две функции, каждая из которых вызовется в случае успешного и неуспешного запроса связанного объекта:
record.getComponent({
success:function(a,b){console.log(a);console.log(b)}
failure:function(a,b){console.log(a);console.log(b)}
});