Records
Хранилище
структурированных записей
— журналов, заметок, любых списков, которые растут со временем. В отличие от
`Storage`
, где один ключ хранит одно значение,
Records
даёт записям стабильные
id
, даты и мягкое удаление, поэтому одну запись можно отредактировать или удалить, не переписывая весь список.
Тип:
multi-child
(генератор виджетов, поддерживает слоты жизненного цикла)
Records collection:"workouts" data:@rows count:@n
ListView.builder itemCount:$n
@Macros id:itemBuilder index:0
Text {rows[index]['title'] ?? ""}
Правило выбора:
если элементы ДОБАВЛЯЮТСЯ со временем — это
Records
.
Скаляр, который пользователь задаёт один раз (тема, дневная цель, флаг), остаётся в
Storage
.
Параметры
| Параметр | Тип | Default | Описание |
|---|---|---|---|
collection
|
string | — |
Имя коллекции. Алиас —
name
|
includeDeleted
|
bool |
false
|
Публиковать и мягко удалённые записи (экран корзины) |
data
|
@
-переменная
|
— | Список записей |
count
|
@
-переменная
|
— | Сколько записей опубликовано |
loaded
|
@
-переменная
|
— |
true
, когда чтение завершилось
|
error
|
@
-переменная
|
— |
Текст ошибки или
null
|
У каждой записи всегда есть служебные поля
id
,
createdAt
,
updatedAt
,
deletedAt
— их проставляет рантайм, писать их самому не нужно.
Дочерние элементы
Дети размечаются по состоянию загрузки, и монтируется только подходящий — так
id:ready
никогда не строится на отсутствующих данных:
Records collection:"notes" data:@notes count:@n
CircularProgressIndicator id:loading
Text "Не удалось прочитать" id:error
ListView.builder id:ready itemCount:$n
...
id:ready
можно не указывать — им становится первый неразмеченный ребёнок.
Запись: ambient-объект
records
Чтение — это элемент, запись — это методы: в выражении нет присваиваемой левой части. Вызывать их нужно из обработчика в форме
(){…}
; голое
{records.add(…)}
— это ВЫРАЖЕНИЕ-ЗНАЧЕНИЕ, оно выполнится один раз при монтировании экрана.
| Метод | Что делает |
|---|---|
records.add(collection, fields)
|
добавить запись |
records.update(collection, id, patch)
|
изменить одну запись; неизвестный
id
— no-op
|
records.remove(collection, id)
|
МЯГКОЕ удаление одной записи (ставит
deletedAt
)
|
records.purge(collection, id)
|
ЖЁСТКОЕ удаление одной записи |
records.removeWhere(collection, (r){…})
|
мягко удалить ВСЕ подходящие под предикат |
records.purgeWhere(collection, (r){…})
|
жёстко удалить все подходящие |
records.clear(collection)
|
очистить одну коллекцию |
records.clearAll()
|
очистить ВСЕ коллекции |
Полные сигнатуры и документация доступны через
reeel_schema {name:"records"}
.
Примеры
Форма, которая сохраняет
@title:""
Records collection:"workouts" data:@rows
Column
TextField value:$title
FilledButton onPressed:(){records.add("workouts", mapOf("title", title, "at", now())); title=""}
Text "Добавить"
Удаление по условию
Область удаления выбирает метод: одна запись —
remove
/
purge
, произвольный фильтр —
*Where
, вся коллекция —
clear
.
// сбросить всё за сегодня — один вызов, один проход
OutlinedButton onPressed:(){records.removeWhere("intake", (r){dateKey(r['at'] ?? 0) == dateKey(today())})}
Text "Сбросить сегодня"
Корзина и восстановление
remove
мягкое, поэтому удалённое ещё доступно с
includeDeleted:true
. Предикат
purgeWhere
— единственный, который ВИДИТ мягко удалённые записи:
Records collection:"notes" includeDeleted:true data:@trash
Column
TextButton onPressed:(){records.purgeWhere("notes", (r){r['deletedAt'] != null})}
Text "Очистить корзину"
Восстановление — это обычный
update
:
records.update("notes", id, mapOf("deletedAt", null))
.
Число из поля ввода
TextField
публикует строку даже при
keyboardType:number
, а
sum
/
sumBy
строки пропускают: записанный текстом объём молча суммируется в ноль. Преобразуйте при записи —
mapOf("volume", num(volume), …)
— или объявите поле в схеме:
Field "volume" type:number
приводит значение при каждой записи и чтении, и уже сохранённые строки читаются числами.
График из записей
Записи разрежены и датированы, а графику нужен плотный ряд чисел.
series(...)
проходит все дни диапазона, подставляя 0 там, где записей не было:
Records collection:"intake" data:@rows
State @week:{series(rows, addDays(today(), -6), today(),
(r){dateKey(r['at'])}, (rs){sumBy(rs, (x){x['ml'] ?? 0})})}
SizedBox height:150
$Bars values:$week color:$theme.primary
Bad practices
Чистая функция ради побочного эффекта
// BAD — map/filter это ЧИСТЫЕ функции. Такой вызов порождает по отдельной
// записи на строку, они гонятся друг с другом, и экран увидит только последнюю.
onPressed:(){map(filter(rows, (r){dateKey(r['at']) == dateKey(today())}), (r){records.remove("intake", r['id'] ?? "")})}
// GOOD — один вызов с предикатом: выборка целиком, одна публикация
onPressed:(){records.removeWhere("intake", (r){dateKey(r['at'] ?? 0) == dateKey(today())})}
Растущий список в одном ключе
Storage
// BAD — у элементов нет id, редактирование одного переписывает весь список
Storage @workouts:[]
// GOOD
Records collection:"workouts" data:@rows
Подписка без значения по умолчанию
// BAD — выводы генератора равны null, пока данные не загрузились
Text {rows[index]['title']}
// GOOD
Text {rows[index]['title'] ?? ""}
Best practices
- Не храните производные значения. Итоги, серии и streak считаются из записей выражением; сохранённая копия рано или поздно разойдётся с журналом.
-
Передавайте объект через маршрут
, а не через глобальную переменную:
id— параметром пути, сам объект — вторым аргументомnavigator.show(...), читать черезRouteData. -
records.clear()без имени коллекции ничего не делает — имя обязательно. -
clearAllнеобратим и срабатывает по одному нажатию: ставьте его за подтверждением.
See also
- `Storage` — скаляры, которые пользователь задаёт один раз
-
Функции выражений
—
filter,groupBy,series, функции дат