Спросите помощника
0/400
Вопрос обрабатывается ИИ-сервисом и сохраняется для улучшения ответов. Не вводите персональные данные.

    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

    menu_baseline