Особенности объявления сгенерированных моделей в коде
Модели в технологии Flexberry Ember определяются “стандартным” для Ember способом. Создаваемая модель наследуется от базового технологического класса и предоставляет возможность применять правила валидации модели.
Генерируемые из Flexberry Designer модели чаще всего имеют следующую структуру
// Импорт для валидации.
import { buildValidations } from 'ember-cp-validations';
// Импорт базового класса для моделей во Flexberry Ember.
import EmberFlexberryDataModel from 'ember-flexberry-data/models/model';
// Импорт модели для работы в офлайн-режиме.
import OfflineModelMixin from 'ember-flexberry-data/mixins/offline-model';
// Проекции, правила валидации и сама модель определяются из соответсвующего миксина.
import {
defineProjections,
ValidationRules,
Model as AgregatorClassMixin
} from '../mixins/regenerated/models/i-i-s-gen-test-agregator-class';
// Подготовка для задания на модель правил валидации данных.
const Validations = buildValidations(ValidationRules, {
dependentKeys: ['model.i18n.locale'],
});
// Непосредственно определение модели.
let Model = EmberFlexberryDataModel.extend(OfflineModelMixin, AgregatorClassMixin, Validations, {
});
// Определение проекций (сами проекции заданы в миксине).
defineProjections(Model);
// Экспорт модели.
export default Model;
Соответствующий модели миксин с таким же именем расположен в папке mixins/regenerated/models
. Его структура следующая:
// Необходимые импорты.
import Mixin from '@ember/object/mixin';
import $ from 'jquery';
import DS from 'ember-data';
import { validator } from 'ember-cp-validations';
// Импорт для объявления проекций.
import { attr, belongsTo, hasMany } from 'ember-flexberry-data/utils/attributes';
// Импорт для перечислимого типа.
import Enum1TypeEnum from '../../../enums/i-i-s-gen-test-enum1-type';
// Объявление атрибутов модели.
export let Model = Mixin.create({
...
});
// Объявление типичных правил валидации.
export let ValidationRules = {
...
};
// Объявление проекций.
export let defineProjections = function (modelClass) {
...
};
Генерируемые из Flexberry Designer модели создаются в папку models
и именуются следующим образом:
- если соответствующий C#-класс на OData-бэкенде называется
NewPlatform.Someproject.Somemodel
, то файл с моделью в клиентском приложении должен называтьсяnew-platform-someproject-somemodel
, - если на OData-бэкенде используется атрибут PublishName для упрощения именования моделей, то наименование пространства имен в этом случае в клиентской модели может отсутствовать (имя клиентской модели будет формироваться соответственно имени в EDM-модели на OData-бакенде).
Объявление модели, имеющей предка
Если у модели есть класс-предок, то для класса-потомка объявление модели будет чуть отличаться:
import $ from 'jquery';
import { buildValidations } from 'ember-cp-validations';
import {
defineBaseModel, // Импорт метода для работы с моделью предка.
defineProjections,
ValidationRules,
Model as Child1Mixin
} from '../mixins/regenerated/models/i-i-s-gen-test-child1';
// Импорт модели предка.
import ParentClassModel from './i-i-s-gen-test-parent-class';
// Импорт правил валидации для модели предка.
import { ValidationRules as ParentValidationRules } from '../mixins/regenerated/models/i-i-s-gen-test-parent-class';
// Определение правил валидации с учётом правил валидации модели предка.
const Validations = buildValidations($.extend({}, ParentValidationRules, ValidationRules), {
dependentKeys: ['model.i18n.locale'],
});
// Модель является расширением ParentClassModel, а не EmberFlexberryDataModel.
let Model = ParentClassModel.extend(Child1Mixin, Validations, {
});
// Определение элементов от базовой модели.
defineBaseModel(Model);
defineProjections(Model);
export default Model;
Соответствующий миксин отличается тем, что добавлено определение defineBaseModel
:
...
export let defineBaseModel = function (modelClass) {
modelClass.reopenClass({
_parentModelName: 'i-i-s-gen-test-parent-class'
});
};
...
Базовые классы и миксины для моделей Flexberry Ember
В технологии Flexberry Ember
доступны следующие базовые модели:
ModelWithoutValidation
- базовая модель с поддержкой проекций и копирования.Model
- базовая модель, наследуемая отModelWithoutValidation
, с поддержкой валидации (генерируемые модели наследуются от неё).OfflineModel
- базовая модель, наследуемая отModel
, для работы в офлайн-режиме.
В технологии Flexberry Ember
доступны следующие базовые миксины для моделей:
CopyableMixin
- миксин для поддержки возможности создания по прототипу, используется в базовой моделиModelWithoutValidation
.AuditModelMixin
- миксин для работы подсистемы аудита, используется в миксинеOfflineModelMixin
.OfflineModelMixin
- миксин, добавляющий свойства для решения вопросов синхронизации, используется в базовой моделиOfflineModel
, а также добавляется в сгенерированную модель.
Правила генерации атрибутов и связей в модели
Атрибуты в моделях определяются “стандартным” для Ember способом, также как и связи.
export let Model = Mixin.create({
// Определение собственных атрибутов.
doubleField: DS.attr('decimal'),
stringField: DS.attr('string'),
// Определение мастера.
myMaster: DS.belongsTo('i-i-s-gen-test-master-for-child1', { inverse: null, async: false }),
// Определение детейлов.
detail1ForChild1: DS.hasMany('i-i-s-gen-test-detail1-for-child1', { inverse: 'child1', async: false }),
detail2ForChild1: DS.hasMany('i-i-s-gen-test-detail2-for-child1', { inverse: 'child1', async: false })
});
createTime
, creator
, editTime
, editor
).Генерируемые типы и трансформации
Дополнительно к стандартным типам данных для атрибутов моделей (string
(строка), number
(число), boolean
(логический тип) и date
(дата)) в технологииFlexberry Ember
были добавлены трансформации:
decimal
(вещественный тип),file
(тип “Файл”),flexberry-enum
(тип для перечислений),guid
(тип “GUID”, который используется по умолчанию для идентификаторов).
inverse-связи в модели
Задание inverse-связи используется, например, при работе с детейлами.
Задание связи от агрегатора к детейлу.
export let Model = Mixin.create({
...
detail1ForChild1: DS.hasMany('i-i-s-gen-test-detail1-for-child1', { inverse: 'child1', async: false }),
});
Задание связи от детейла к агрегатору.
export let Model = Mixin.create({
...
child1: DS.belongsTo('i-i-s-gen-test-child1', { inverse: 'detail1ForChild1', async: false })
});
Первичный ключ в модели
Первичные ключи объекта не задаются в модели явно.
В клиентском коде обращения к первичному ключу можно выполнить через свойство id
. Как называется соответствующее свойство на сервере, определяется в сериализаторе.
import { Serializer as MasterForAgregatorSerializer } from
'../mixins/regenerated/serializers/i-i-s-gen-test-master-for-agregator';
import __ApplicationSerializer from './application';
export default __ApplicationSerializer.extend(MasterForAgregatorSerializer, {
/**
* Имя поля, где хранится первичный ключ модели.
*/
primaryKey: '__PrimaryKey'
});
Первичные ключи моделей в Ember
-приложениях всегда являются строками, но на сервере это поведение можно изменить. При изменении типа первичного ключа на сервере необходимо переопределить статическое свойство idType
в классе модели:
import EmberFlexberryDataModel from 'ember-flexberry-data/models/model';
...
let Model = EmberFlexberryDataModel.extend( ... );
...
Model.reopenClass({
idType: '...',
});
export default Model;
Устанавливается свойство idType
при помощи статической функции defineIdType
в базовой технологической модели:
defineIdType: function (newIdType) {
this.reopenClass({
idType: newIdType,
});
},
Вызвать этот метод можно следующим образом:
Model.defineIdType('string');
Тип первичного ключа - это метаданные модели, поэтому свойство idType
определено именно в модели, а не, например, в адаптере.
Получить тип ключа можно через метод getMeta
утилиты information
(см. ниже).
В языке запросов тип ключа учитывается автоматически, и при построении запросов к OData-бакенду значения ключей в URL запросов “окавычиваются” только в том случае, если тип ключа string
.
string
, guid
и number
. В других случаях при построении запросов к OData-бакенду будет выбрасываться исключение.guid
.Проекции в моделях
Проекции используются для определения, какие свойства будут запрошены с сервера или отправлены на него. Определение проекций для модели осуществляется следующим образом:
Model.defineProjection('<Имя проекции>', '<Имя класса>', '<Атрибуты проекции>');
- Имя проекции может быть произвольным. Чаще всего для форм редактирования и создания используют представления с именем “<Короткое имя="" класса="">E", а для [списковых форм](efd3_listform.html) - "<Короткое имя="" класса="">L" (например, для модели `new-platform-gen-test-agregator-class` это будут `AgregatorClassE` и `AgregatorClassL`).Короткое>Короткое>
- Имя класса - это имя текущего класса, для которого определяется модель. Например,
new-platform-someproject-somemodel
. - Атрибуты проекции - это атрибуты модели и зависимых моделей, которые входят в проекцию.
В примере ниже для модели i-i-s-gen-test-agregator-class
определяется проекция AgregatorClassE
.
// Для модели i-i-s-gen-test-agregator-class определяется проекция AgregatorClassE.
modelClass.defineProjection('AgregatorClassE', 'i-i-s-gen-test-agregator-class', {
// Добавлен атрибут перечислимого типа.
enum1Field: attr('Перечисление 1', { index: 0 }),
// Добавлена ссылка на мастера типа i-i-s-gen-test-child2.
child2: belongsTo('i-i-s-gen-test-child2', 'Мастер потомок', {
dateTimeField: attr('~', { index: 2, hidden: true })
}, { index: 1, displayMemberPath: 'dateTimeField' }),
// Добавлена ссылка на мастера типа i-i-s-gen-test-master-for-agregator.
masterForAgregator: belongsTo('i-i-s-gen-test-master-for-agregator', 'Master for agregator', {
enum2Field: attr('~', { index: 4, hidden: true })
}, { index: 3, displayMemberPath: 'enum2Field' }),
// Добавлена ссылка на детейл типа i-i-s-gen-test-detail-for-agregator.
detailForAgregator: hasMany('i-i-s-gen-test-detail-for-agregator', 'Детейл агрегатора', {
// Добавлен атрибут детейла целого типа.
detailIntField: attr('Целое', { index: 0 }),
// Добавлена ссылка на мастера детейла типа i-i-s-gen-test-master-for-agregator.
masterForAgregator: belongsTo('i-i-s-gen-test-master-for-agregator', 'Мастеровое', {
enum2Field: attr('~', { index: 2, hidden: true })
}, { index: 1, displayMemberPath: 'enum2Field' })
})
});
enum1Field: attr('Перечисление 1', { index: 0 })
- в проекцию моделиi-i-s-gen-test-agregator-class
добавляется свойствоenum1Field
моделиi-i-s-gen-test-agregator-class
с заголовкомПеречисление 1
.child2: belongsTo('i-i-s-gen-test-child2', 'Мастер потомок', { ... }, { index: 1, displayMemberPath: 'dateTimeField' })
- в проекцию моделиi-i-s-gen-test-agregator-class
добавляется ссылка на мастераchild2
типаi-i-s-gen-test-child2
с заголовком ‘Мастер потомок’, при этом на форме у данного свойства будет отображаться атрибут мастераdateTimeField
. Сам же атрибут мастераdateTimeField
, добавляемый кодомdateTimeField: attr('~', { index: 2, hidden: true })
, скрыт (такое скрытие свойств мастеров часто используется для работы лукапов).detailForAgregator: hasMany('i-i-s-gen-test-detail-for-agregator', 'Детейл агрегатора', { ... })
- в проекцию моделиi-i-s-gen-test-agregator-class
добавляется ссылка на детейлыdetailForAgregator
типаi-i-s-gen-test-detail-for-agregator
с заголовком ‘Детейл агрегатора’. Из детейлов в представление попадают собственные свойства детейла, а также ссылка на мастера детейлов.
Генерируемые сериализаторы для моделей
Метаданные модели и вспомогательный класс information
Вспомогательный класс Information позволяет получать метаданные модели. Список доступных методов можно посмотреть в автодокументации.
Ниже представлен пример использования Information: в роуте форм создания определяется тип поля stringField
, и соответствующее значение записывается в поле stringField
созданной модели.
import EditFormNewRoute from 'ember-flexberry/routes/edit-form-new';
// Экспорт вспомогательного класса для последующей работы с ним.
import Information from 'ember-flexberry-data/utils/information';
export default EditFormNewRoute.extend({
modelProjection: 'Child1E',
modelName: 'i-i-s-gen-test-child1',
templateName: 'i-i-s-gen-test-child1-e',
afterModel(resolvedModel, transition){
this._super(...arguments);
// Создание экземпляра класса Information. Во Flexberry-эмбер приложениях в роутах и контроллерах инжектится сервис store и явным образом его получать не требуется.
let information = new Information(this.get('store'));
// Получение типа поля 'stringField' созданной модели.
let fieldType = information.getType(this.get('modelName'), 'stringField');
// Задание типа поля в качестве значения 'stringField'.
resolvedModel.set('stringField', fieldType);
}
});
Вспомогательные модели Flexberry Ember
Вспомогательные модели Flexberry Ember включают:
- модели для аудита. Структура хранения данных аудита аналогична используемой во Flexberry ASP.Net. В настоящее время ведётся разработка нового варианта аудита для
Flexberry Ember
. - модели для полномочий.
flexberry-adv-limit
- модель для задания ограничений.i-i-s-caseberry-logging-objects-application-log
- модель для сервиса логирования.new-platform-flexberry-flexberry-user-setting
- модель для сервиса пользовательских настроек.new-platform-flexberry-services-lock
- модель для сервиса пессимистических блокировок.
Создание динамических моделей
Если необходимо зарегистрировать и использовать новую модель во время выполнения программы, то это можно сделать динамически. Изначально нужно иметь описание самой модели. Например:
let dynamicModel = {
"modelName": "dynamic-model",
"attrs": [
{
"name": "text",
"type": "string",
"notNull": false,
"defaultValue": "",
"stored": true
}
],
"projections": [
{
"name": "BaseE",
"attrs": [
{
"name": "text",
"caption": "Text",
"hidden": false,
"index": 0,
}
]
}
]
};
Тогда вызов можно определить следующим образом:
import { getOwner } from '@ember/application';
import { dynamicModelRegistration } from 'dummy/utils/create-dynamic-models';
/* ... */
dynamicModelRegistration(dynamicModel, getOwner(this));
var base = store.createRecord('dynamic-model', { text: 'dynamic-model-text'});
/* ... */
Далее необходимо реализовать функцию регистрации новой модели по заданному описанию. Например:
import { isNone } from '@ember/utils';
import DS from 'ember-data';
import { attr } from 'ember-flexberry-data/utils/attributes';
import Model from 'ember-flexberry-data/models/model';
// Устанавливаем дефолтные значения для атрибутов модели и возвращаем модель.
function createModel(attrs) {
if (isNone(attrs)) {
return;
}
let model = {};
attrs.forEach((attribute) => {
model[attribute.name] = DS.attr(attribute.type, { required: attribute.notNull });
});
let modelResult = Model.extend(model);
return modelResult;
}
// Устанавливаем дефолтьные значения для атрибутов в представлении
function createProjection(projectionObj) {
let modelProjection = { };
projectionObj.attrs.forEach((attribute) => {
modelProjection[attribute.name] = attr('');
});
return modelProjection;
}
let dynamicModelRegistration = function(dynamicModelObj, owner) {
let modelRegistered = owner.hasRegistration(`model:${dynamicModelObj.modelName}`);
// Проверяем, что модель еще не была зарегистрирована
if (!modelRegistered) {
let model = createModel(dynamicModelObj.attrs);
// Создание представлений для модели
dynamicModelObj.projections.forEach((projection) => {
model.defineProjection(projection.name, dynamicModelObj.modelName, createProjection(projection));
});
// Регистрируем модель
owner.register(`model:${dynamicModelObj.modelName}`, model);
}
}
export {
dynamicModelRegistration
};
Пример с реализацией на тестовом стенде.