Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions docs/АктивнаяЗапись.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## АктивнаяЗапись

Сущность, созданная менеджером (`СоздатьЭлемент`) или прочитанная из базы, — запись с методами сохранения, чтения и удаления себя.

Тип записи регистрируется в системе типов один раз на тип сущности под именем `ЗаписьСущности_<ТипСущности>`: у всех записей одного типа сущности один и тот же тип, `ТипЗнч(Запись)` возвращает его, а не `Сценарий`. Исходный тип сущности дает `АктивнаяЗапись.ТипСущности(Запись)`. Хранилище и модель данных запись получает от создавшего ее менеджера, поэтому записи разных менеджеров одного типа сущности сохраняются каждая в свой источник.

### Сохранить

```bsl
Expand All @@ -8,12 +12,12 @@
Процедура Сохранить()
```

### Получить
### Прочитать

```bsl
// Прочитать текущую сущность. Для чтения в сущностей должно быть заполнено поле, являющееся идентификатором.
// Прочитать текущую сущность. Для чтения у сущности должно быть заполнено поле, являющееся идентификатором.
//
Процедура Получить()
Процедура Прочитать()
```

### Удалить
Expand Down
66 changes: 52 additions & 14 deletions src/internal/Модули/АктивнаяЗапись.os
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#Использовать decorator
#Использовать collectionos

// СинхронизированнаяКарта - Имена зарегистрированных типов записей: ключ - тип сущности, значение - имя типа.
Перем ТипыЗаписей;

Функция ТипСущности(Сущность) Экспорт
ТипСущности = ТипЗнч(Сущность);
Если ТипСущности = Тип("Сценарий") Тогда
ТипСущности = ОбработкаДекоратора.ИсходныйТип(Сущность);
Если ОбработкаДекоратора.ЭтоДекоратор(Сущность) Тогда
Возврат ОбработкаДекоратора.ИсходныйТип(Сущность);
КонецЕсли;

Возврат ТипСущности;
Возврат ТипЗнч(Сущность);
КонецФункции

// Создает экземпляр сущности, расширенный методами паттерна Active Record.
Expand All @@ -15,6 +18,9 @@
// поэтому в методы записи его передавать не нужно, а операцию хранилище все равно делегирует
// своему менеджеру.
//
// Тип записи регистрируется в системе типов один раз на тип сущности: декоратор компилируется
// при первом создании, дальше экземпляры создаются как обычные объекты этого типа.
//
// Параметры:
// ОбъектМодели - ОбъектМодели - Модель сущности, экземпляр которой создается.
// ХранилищеСущностей - ХранилищеСущностей - Хранилище типа этой сущности.
Expand All @@ -23,32 +29,64 @@
// Произвольный - Экземпляр класса сущности с методами Прочитать, Сохранить и Удалить.
//
Функция Создать(ОбъектМодели, ХранилищеСущностей) Экспорт
Сущность = Новый(ОбъектМодели.ТипСущности());
Запись = Новый(ИмяТипаЗаписи(ОбъектМодели.ТипСущности()));

// Хранилище и модель у каждого менеджера свои, поэтому они привязываются к экземпляру,
// а не к зарегистрированному типу.
Рефлектор = Новый Рефлектор();
Рефлектор.УстановитьСвойство(Запись, "_ХранилищеСущностей", ХранилищеСущностей);
Рефлектор.УстановитьСвойство(Запись, "_ОбъектМодели", ОбъектМодели);

Возврат Запись;
КонецФункции

Декоратор = Новый ПостроительДекоратора(Сущность)
// Регистрирует тип записи для типа сущности. Вызывается картой типов под ее блокировкой.
//
// Параметры:
// ТипСущности - Тип - Тип сущности.
//
// Возвращаемое значение:
// Строка - Имя зарегистрированного типа записи.
//
Функция ЗарегистрироватьТипЗаписи(ТипСущности) Экспорт
ИмяТипа = "ЗаписьСущности_" + ТипСущности;

Построитель = Новый ПостроительДекоратора(ТипСущности)
.Импорт(Новый Импорт("decorator"))

.Поле(Новый Поле("_ХранилищеСущностей")
.ЗначениеПоУмолчанию(ХранилищеСущностей))
.Поле(Новый Поле("_ХранилищеСущностей"))

.Поле(Новый Поле("_ОбъектМодели")
.ЗначениеПоУмолчанию(ОбъектМодели))
.Поле(Новый Поле("_ОбъектМодели"))

.Метод(Новый Метод("Прочитать")
.Публичный()
.ТелоМетода("_ДанныеСущности = _ХранилищеСущностей.ПолучитьОдно(_ОбъектМодели.ПолучитьЗначениеИдентификатора(ЭтотОбъект));
|ОбработкаДекоратора.СинхронизироватьПоля(_ДанныеСущности, ЭтотОбъект);"))


.Метод(Новый Метод("Сохранить")
.Публичный()
.ТелоМетода("_ХранилищеСущностей.Сохранить(ЭтотОбъект);"))

.Метод(Новый Метод("Удалить")
.Публичный()
.ТелоМетода("_ХранилищеСущностей.Удалить(ЭтотОбъект);"))
.ТелоМетода("_ХранилищеСущностей.Удалить(ЭтотОбъект);"));

Построитель.ЗарегистрироватьВСистемеТипов(ИмяТипа);

.Построить();
Возврат ИмяТипа;
КонецФункции

Функция ИмяТипаЗаписи(ТипСущности)
ИмяТипа = ТипыЗаписей.ПолучитьИлиУмолчание(ТипСущности, Неопределено);
Если ИмяТипа <> Неопределено Тогда
Возврат ИмяТипа;
КонецЕсли;

Возврат Декоратор;
// Проверка и регистрация под одной блокировкой: тип регистрируется ровно один раз на процесс.
Возврат ТипыЗаписей.ВычислитьЕслиОтсутствует(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Читать под блокировкой на горячем пути очень дорого. Надо менять на CoW-карта здесь подойдёт лучше.

ТипСущности,
"(ТипСущности) -> АктивнаяЗапись.ЗарегистрироватьТипЗаписи(ТипСущности)"
).Получить();
КонецФункции

ТипыЗаписей = Новый СинхронизированнаяКарта(Новый КартаСоответствие());
104 changes: 104 additions & 0 deletions tests/АктивнаяЗапись.os
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Записи сущностей: один зарегистрированный тип на тип сущности, привязка записи к своему менеджеру.
//
// BSLLS:LatinAndCyrillicSymbolInWord-off
// BSLLS:DuplicateStringLiteral-off

#Использовать ".."
#Использовать "utils"

// ИсточникДанных - Источники, созданные тестом; закрываются после каждого теста.
Перем Источники;

&ПередКаждым
Процедура ПередЗапускомТеста() Экспорт
ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект");
Источники = Новый Массив;
КонецПроцедуры

&ПослеКаждого
Процедура ПослеЗапускаТеста() Экспорт
Для Каждого Источник Из Источники Цикл
Источник.Закрыть();
КонецЦикла;
КонецПроцедуры

Функция НовыйМенеджер(ИмяИсточника)
Источник = Новый ИсточникДанных(ИмяИсточника, Тип("КоннекторInMemory"), ИмяИсточника);
Источник.УстановитьАвтоЗакрытие(Ложь);
Источники.Добавить(Источник);

Менеджер = Новый МенеджерСущностей(Источник);
Менеджер.ДобавитьКлассВМодель(Тип("ПростойОбъект"));
Менеджер.Инициализировать();

Возврат Менеджер;
КонецФункции

&Тест
Процедура ЗаписиОдногоТипаСущностиИмеютОдинТип() Экспорт
Менеджер = НовыйМенеджер("записи одного типа");

Первая = Менеджер.СоздатьЭлемент(Тип("ПростойОбъект"));
Вторая = Менеджер.СоздатьЭлемент(Тип("ПростойОбъект"));

Ожидаем.Что(ТипЗнч(Первая), "тип первой записи").Равно(Тип("ЗаписьСущности_ПростойОбъект"));
Ожидаем.Что(ТипЗнч(Вторая), "тип второй записи").Равно(ТипЗнч(Первая));
Ожидаем.Что(Первая = Вторая, "записи - разные объекты").ЭтоЛожь();
КонецПроцедуры

&Тест
Процедура ЗаписьСохраняетЧитаетИУдаляетСебя() Экспорт
Менеджер = НовыйМенеджер("методы записи");

Запись = Менеджер.СоздатьЭлемент(Тип("ПростойОбъект"));
Запись.Идентификатор = "1";
Запись.Поле = "до";
Запись.Сохранить();

Найденная = Менеджер.ПолучитьОдно(Тип("ПростойОбъект"), "1");
Ожидаем.Что(Найденная.Поле, "поле после сохранения").Равно("до");

Найденная.Поле = "после";
Найденная.Сохранить();
Запись.Прочитать();
Ожидаем.Что(Запись.Поле, "поле после чтения").Равно("после");

Запись.Удалить();
Ожидаем.Что(Менеджер.Получить(Тип("ПростойОбъект")).Количество(), "записей после удаления").Равно(0);
КонецПроцедуры

&Тест
Процедура ЗаписьПривязанаКСвоемуМенеджеру() Экспорт
Первый = НовыйМенеджер("первый менеджер");
Второй = НовыйМенеджер("второй менеджер");

ЗаписьПервого = Первый.СоздатьЭлемент(Тип("ПростойОбъект"));
ЗаписьПервого.Идентификатор = "1";
ЗаписьПервого.Поле = "первый";
ЗаписьПервого.Сохранить();

ЗаписьВторого = Второй.СоздатьЭлемент(Тип("ПростойОбъект"));
ЗаписьВторого.Идентификатор = "2";
ЗаписьВторого.Поле = "второй";
ЗаписьВторого.Сохранить();

Ожидаем.Что(Первый.Получить(Тип("ПростойОбъект")).Количество(), "записей у первого").Равно(1);
Ожидаем.Что(Первый.ПолучитьОдно(Тип("ПростойОбъект"), "1").Поле, "поле записи первого").Равно("первый");
Ожидаем.Что(Второй.Получить(Тип("ПростойОбъект")).Количество(), "записей у второго").Равно(1);
Ожидаем.Что(Второй.ПолучитьОдно(Тип("ПростойОбъект"), "2").Поле, "поле записи второго").Равно("второй");
КонецПроцедуры

&Тест
Процедура МенеджерПринимаетЗаписьПоТипуСущности() Экспорт
Менеджер = НовыйМенеджер("запись через менеджер");

Запись = Менеджер.СоздатьЭлемент(Тип("ПростойОбъект"));
Запись.Идентификатор = "1";
Запись.Поле = "через менеджер";
Менеджер.Сохранить(Запись);

Ожидаем.Что(Менеджер.ПолучитьОдно(Тип("ПростойОбъект"), "1").Поле, "поле").Равно("через менеджер");

Менеджер.Удалить(Запись);
Ожидаем.Что(Менеджер.Получить(Тип("ПростойОбъект")).Количество(), "записей после удаления").Равно(0);
КонецПроцедуры