Модуль basis.entity
расширяет функциональность модуля basis.data
и предназначен для описания типизированных моделей данных. Он предоставляет функции-конструкторы, производящие классы наследников basis.data.Object
и basis.data.ReadOnlyDataset
, а также функции-хелперы для решения различных задач.
Дополнительная функциональность касается, в основном, потомков basis.data.Object
и включает в себя:
- фиксированный набор полей
- значения по умолчанию
- нормализация значений
- вычисляемые поля
- индексы
- возможность накапливать и откатывать изменения
Под типом понимается связка сущностей:
- функция-обертка (
EntityTypeWrapper
) - конструктор типа (
EntityTypeConstructor
) - класс
В основном приходится иметь дело только с оберткой, которая уже пользуется остальным. Иногда необходимо обращаться к классу для его расширения. Обращение к конструктору происходит крайне редко и можно о нем не знать, но он выполняет основную работу по конструированию типа, содержит информацию о типе и обеспечивает часть механизмов его работы.
Так как основная работа осуществляется с функцией-оберткой, то обычно под «типом» подразумеваются именно такие функции.
Создаваемый таким образом класс является наследником basis.entity.BaseEntity
, а он, в свою очередь, наследником basis.data.Object
. Таким образом, экземпляры наследуют функциональность basis.data.Object
и хранят свои данные в поле data
как ключ-значение.
Объявление типа (его создание) выполняется с помощью функции createType
. Этой функции передается конфигурация будущего типа, а результатом выполнения является функция-обертка (тип).
var entity = basis.require('basis.entity');
var MyType = entity.createType({
name: 'MyType',
fields: {
// описание полей
}
});
Несмотря на то, что такие функции не являются конструктором (классом) в обычном смысле, и не используются с оператором new
, их принято именовать с большой буквы, как классы. Это связанно с тем, что такие функции являются фабриками экземплряров, они создают или обновляют уже существующие экземпляры.
Основной «настройкой» является fields
— набор полей, а точнее фиксированный набор ключей поля data
. Его нельзя изменить после объявления, и все экземпляры типа обязательно будут содержать заданный набор ключей.
Не менее важным является указание названия типа — name
. Это не только помогает в разработке и отладке, но также позволяет осуществлять позднее связывание типов между собой. Если название типа не задается, оно генерируется автоматически. Нельзя объявить тип с именем, которое уже занято. В случае конфликта имен имя для нового типа игнорируется (генерируется автоматически). Таким образом, для одного имени может быть создан только один тип.
Обычно при объявлении типа достаточно указать его название и описать поля. Поэтому у createType
есть сокращенный синтаксис, и предыдущий пример может быть описан так:
var entity = basis.require('basis.entity');
var MyType = entity.createType('MyType', {
// описание полей
});
При объявлении типа доступны следующие опции:
name
— имя типа, используется для отладки (оно фигурирует в инструментах разработки) и ссылки на тип при объявлении других типов;fields
— описание полей (см. описание полей);aliases
— псевдонимы полей, для упрощения конвертации одних названий полей в другие, например, при разборе ответа от сервера;constrains
— ограничения полей (по аналогии сconstrains
в база данных);all
— позволяет задать конфигурацию для коллекции экземпляров типа (см.Type.all
);index
— позволяет задать объект, обслуживающий индекс, значение должно быть экземпляромbasis.entity.Index
; обычно не нужно его определять, он создается автоматически в случае необходимости;singleton
— булево значение, определяющее, является ли тип синглтоном; если значение равноtrue
(по умолчанию равноfalse
), то максимум может быть создан только один экземпляр типа, автоматические индексы при этом игнорируются;
Type(number or string) Type(object)
Поля описываются с помощью поля fields
в конфигурации типа. Либо такое описание передается отдельным параметром функции basis.entity.createType()
(см. создание типа).
Описанием поля является объект, в котором можно указать следующие опции:
type
— обычно это функция нормализации значения, которая приводит новое значение к необходимому виду (типу) (см. типы полей); также в качестве типа могут быть заданы массив или строка, которые заменяются на функции при создании типа;defValue
— значение по умолчанию (см значение по умолчанию);id
— булево значение, определяющее, что поле является идентификатором (первичным ключом) или его частью (если задано для нескольких полей, образуется составной ключ);index
— индекс, к которому относится поле; используется для образования дополнительных ключей (см индексы);calc
— функция вычисления; если задается эта опция, то значение такого поля не является самостоятельным, а является результатом вычисления от других полей; в этом случае, поле являетсятолько для чтения
(read-only
), его значение обновляется автоматически при изменении других полей (см. вычисляемые поля).
Описанием поля может являться не только объект, но и другие типы значений. В таком случае, описание считается сокращенным и разворачивает в объект конфигурации автоматически при создании типа. В качестве описания могут быть указаны: функция, массив или строка. Такие «описания» становится типом поля:
var Foo = basis.entity.createType('Foo', {
field: String,
enum: [1, 2, 3],
nested: 'Bar'
});
// эквивалентно
var Foo = basis.entity.createType('Foo', {
field: {
type: String
},
enum: {
type: [1, 2, 3]
},
nested: {
type: 'String'
}
});
Также в качестве описания поля могут выступать специальные функции модуля: basis.entity.IntId
, basis.entity.NumberId
и basis.entity.StringId
. Эти функции так же становятся «типом», но дополнительно определяют, что поле является первичным ключом (или его частью).
Значение по умолчанию задается настройкой defValue
(сокращение от default value
) в описании поля. Это может быть любое значение или функция. Такие значения используются только в том случае, если в данных переданных конструктору экземпляра типа не указано соответствующее поле (field in data == false
).
var Foo = basis.entity.createType('Foo', {
field: {
type: String,
defValue: 'example'
},
bar: Number
});
console.log(Foo({}).data);
// > { bar: 0, field: 'example' }
console.log(Foo({ bar: 1 }).data);
// > { bar: 1, field: 'example' }
console.log(Foo({ field: 'test' }).data);
// > { bar: 0, field: 'test' }
console.log(Foo({ field: undefined }).data);
// > { bar: 0, field: 'undefined' }
Важно помнить, что значение defValue
не пропускается через функцию нормализации объявленную в type
(исключением являются перечисляемые поля, для которых делается проверка, что defValue
находится среди возможных вариантов). Считается, что в defValue
хранится нормализованное значение (значение необходимого типа).
Вероятно, что в будущих версиях
basis.js
это изменится и значениеdefValue
будет пропускаться через функцию нормализации, чтобы избежать ошибок в объявлении типа и несогласованности значений.
Обычно нет необходимости явно указывать значение по умолчанию. Если опция defValue
не указана, то значением по умолчанию будет являться результат выполнения функции нормализации (type
) без аргументов. Например, для Boolean
это будет false
, для String
– пустая строка, для перечисляемых полей – первое значение, а для специальных индексных функций (например, basis.entity.IntId
) – null
.
Если в качестве значения defValue
задается функция, то эта функция будет выполняться каждый раз, когда нужно получить значение по умолчанию. Такой функции передается объект первичных данных, который был передан конструктору типа. Этот объект можно использовать для генерации значения по умолчанию. Крайне не рекомендуется модифицировать этот объект. Если необходимо вносить изменения в объект первичных данных, необходимо переопределить метод init
класса типа и делать это в рамках этого метода.
var Foo = basis.entity.createType('Foo', {
field: {
type: String,
defValue: function(data){
console.log('initial data:', data);
return 'default' + (data.bar || 0);
}
},
bar: Number
});
console.log(Foo({}).data);
// > initial data: {}
// > { bar: 0, field: 'default0' }
console.log(Foo({ bar: 1 }).data);
// > initial data: { bar: 1 }
// > { bar: 1, field: 'default1' }
console.log(Foo({ field: 'test' }).data);
// > { bar: 0, field: 'test' }
console.log(Foo({ field: undefined }).data);
// > { bar: 0, field: 'undefined' }
Boolean, Number, String
get/set/update
require('basis.entity').validate()