How-to: Нумерация
Предположим есть некоторый набор книг. Для каждой из них определяется номер как целое число.
CLASS Book 'Книга';
number 'Номер' = DATA INTEGER (Book) IN id;
name 'Название' = DATA ISTRING[50] (Book) IN id;
Реализуем свойство, которое будет по номеру находить книгу. Оно может быть полезно, например, при импорте данных, в котором книга идентифицируется своим номером. При помощи него можно получать ссылку на объект книги, получив параметром ее номер.
book (INTEGER number) = GROUP AGGR Book b BY number(b);
bookExists (INTEGER number) {
IF book(number) THEN
MESSAGE 'Книга с номером ' + number + ' существует. Ее название : ' + name(book(number));
ELSE
MESSAGE 'Книги с номером ' + number + ' не существует';
}
Оператор GROUP AGGR
автоматически добавляет ограничение на уникальность номера. При попытки записать в базу данных повторный номер будет выдано сообщение об ошибке.
Добавляем событие, которое будет автоматически проставлять книге номер, равный следующий за максимальным из существующих в базе данных.
WHEN SET(Book b IS Book) AND NOT number(b) DO {
number(b) <- (GROUP MAX number(Book bb)) (+) 1;
}
Событие будет вызвано в момент сохранения создания книги в базу данных в той же транзакции.
В некоторых ситуациях существует необходимость делать разную нумерацию для одного и того же объекта. Для этой цели можно добавить специальный класс Numerator
.
CLASS Numerator 'Нумератор';
name 'Наименование' = DATA ISTRING[50] (Numerator) IN id;
value = DATA INTEGER (Numerator);
В свойстве value
будет храниться текущее значение нумератора, которое будет записываться в номер нужного объекта. Чтобы достичь этого, для объекта (например, заказ) задается ссылка на соответствующей нумератор. В момент создания объекта, если она задана, то нужно автоматически проставить номеру заказа текущее значение нумератора и увеличить его на один.
CLASS Order 'Заказ';
number 'Номер' = DATA INTEGER (Order) IN id;
numerator 'Нумератор' = DATA Numerator (Order);
WHEN CHANGED(numerator(Order o)) AND NOT CHANGED(number(o)) DO {
number(o) <- value(numerator(o));
value (Numerator n) <- value(n) (+) 1 WHERE n == numerator(o);
}
В условии события проверяется на то, что номер не был изменен, чтобы не изменять его, если пользователь вручную задал номер (или он проставился при импорте).
Важным отличием работы через нумератор от "проставления максимального значения плюс один" является обработка одновременного добавления объектов. Во втором случае, если два пользователя одновременно создадут объекты, то последнему в момент сохранения будет выдано сообщение о повторении номера, и он будет вынужден вручную сохранить повторно. Изменения, сделанные во всех событиях таким образом, будут "откачены", и повторное сохранение сгенерирует новый номер. В случае с нумератором, транзакция последнего пользователя получит CONFLICT UPDATE на поле value
для нумератора (так как обе транзакции меняют поле одной строки в базе данных). Дальше система автоматически откатит транзакцию и все изменения, сделанные в событии, и начнет ее проводить повторно без вмешательства пользователя. Таким образом пользователь заметит лишь возможно в два раза медленнее сохранение, но никаких его дополнительных действий не потребуется.
Для того, чтобы пользователю не приходилось постоянно выбирать нумератор для заказа, можно ввести свойство без входов, которое будет указывать на нумератор по умолчанию. После этого добавить событие, которое будет автоматически проставлять нумератор, в случае если пользователь не выбрал его вручную.
defaultNumerator 'Нумератор по умолчанию' = DATA Numerator();
WHEN SET(Order o IS Order) AND NOT CHANGED(numerator(o)) DO
numerator(o) <- defaultNumerator();