Практическая работа 01 · после модуля 1

Справочник по MongoDB · практика · после модуля 1

ПР-01

ПР-01

Каталог товаров:
витрина и правки

Две задачи, которые бэкендер решает в первую неделю на проекте: отдать фронтенду страницу списка товаров и провести приёмку поставки — с отчётом, повторным запуском и проверкой, что ничего лишнего не изменилось.

После модуля
1 · подключение и первые данные
Базы
shop — читаем, sandbox — меняем
Время
90 минут
Результат
solution.py и README.md с числами
Результат
solution.cpp и README.md с числами
Результат
solution.go и README.md с числами
Результат
solution.rb и README.md с числами

github.com/MaximBytecamp/mongodb-practice → practice01

В репозитории — задание, разобранное решение на четырёх языках (solution.py, solution.cpp, solution.go, solution.rb) и одна самопроверка check.py: она смотрит не на код, а на состояние базы после работы, поэтому годится для любого языка. Стенд и данные там же.

PowerShellс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice\practice01
docker compose run --rm reset          # чистые данные: 21 товар, 120 заказов
# без Docker: bash ../stend/load.sh в Git Bash
python solution.py             # решение
python check.py                # проверка, одна на все языки
Терминалс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice/practice01
bash ../stend/load.sh          # чистые данные: 21 товар, 120 заказов
python3 solution.py            # решение
python3 check.py               # проверка, одна на все языки
Терминалс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice/practice01
bash ../stend/load.sh          # чистые данные: 21 товар, 120 заказов
python3 solution.py            # решение
python3 check.py               # проверка, одна на все языки
PowerShellс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice\practice01
# драйвер C++ под Windows собирается долго — всё делаем в контейнерах
docker compose run --rm reset
docker compose run --rm cpp practice01/solution.cpp
docker compose run --rm python practice01/check.py   # проверка, одна на все языки
Терминалс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice/practice01
bash ../stend/load.sh          # чистые данные: 21 товар, 120 заказов

g++ -std=c++17 solution.cpp -o solution \
    $(pkg-config --cflags --libs libmongocxx1)   # brew install mongo-cxx-driver
./solution                     # решение
python3 check.py               # проверка, одна на все языки
Терминалс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice/practice01
# в пакетах дистрибутива драйвер C++ старый — всё делаем в контейнерах
docker compose run --rm reset
docker compose run --rm cpp practice01/solution.cpp
docker compose run --rm python practice01/check.py   # проверка, одна на все языки
PowerShellс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice\practice01
docker compose run --rm reset          # чистые данные: 21 товар, 120 заказов
# без Docker: bash ../stend/load.sh в Git Bash
go run solution.go             # решение
python check.py                # проверка, одна на все языки
Терминалс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice/practice01
bash ../stend/load.sh          # чистые данные: 21 товар, 120 заказов
go run solution.go             # решение
python3 check.py               # проверка, одна на все языки
Терминалс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice/practice01
bash ../stend/load.sh          # чистые данные: 21 товар, 120 заказов
go run solution.go             # решение
python3 check.py               # проверка, одна на все языки
PowerShellс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice\practice01
docker compose run --rm reset          # чистые данные: 21 товар, 120 заказов
# без Docker: bash ../stend/load.sh в Git Bash
ruby solution.rb               # решение
python check.py                # проверка, одна на все языки
Терминалс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice/practice01
bash ../stend/load.sh          # чистые данные: 21 товар, 120 заказов
ruby solution.rb               # решение
python3 check.py               # проверка, одна на все языки
Терминалс нуля до проверки
git clone https://github.com/MaximBytecamp/mongodb-practice.git
cd mongodb-practice/practice01
bash ../stend/load.sh          # чистые данные: 21 товар, 120 заказов
ruby solution.rb               # решение
python3 check.py               # проверка, одна на все языки

Сервер или драйвер не ставится — те же шаги выполняются в контейнерах, команды одинаковые в PowerShell и bash. Как поднять стенд и что делать при ошибках — глава 1.1а.

Терминалчерез Docker, из корня репозитория
docker compose up -d                                   # один раз: сервер и базы курса
docker compose run --rm reset                          # чистые данные: 21 товар, 120 заказов
docker compose run --rm python practice01/solution.py
docker compose run --rm python practice01/check.py
Терминалчерез Docker, из корня репозитория
docker compose up -d                                   # один раз: сервер и базы курса
docker compose run --rm reset                          # чистые данные: 21 товар, 120 заказов
docker compose run --rm cpp practice01/solution.cpp
docker compose run --rm python practice01/check.py
Терминалчерез Docker, из корня репозитория
docker compose up -d                                   # один раз: сервер и базы курса
docker compose run --rm reset                          # чистые данные: 21 товар, 120 заказов
docker compose run --rm go practice01/solution.go
docker compose run --rm python practice01/check.py
Терминалчерез Docker, из корня репозитория
docker compose up -d                                   # один раз: сервер и базы курса
docker compose run --rm reset                          # чистые данные: 21 товар, 120 заказов
docker compose run --rm ruby practice01/solution.rb
docker compose run --rm python practice01/check.py

§1Что уже умеем

В работе не появится ни одного оператора, которого не было в модуле 1. Вот весь инструмент, который понадобится:

find + проекцияДостать нужные документы и только нужные поля.глава 1.4–1.5
sort · skip · limitПорядок и страницы.глава 1.6
count_documentsСколько всего — для «страница 2 из 5» и для проверки перед правкой.глава 1.6
$set · $incЗаписать значение и прибавить на сервере.глава 1.7
insert_many · delete_manyЗавести новинки и снять с продажи.главы 1.3 и 1.8

Один оператор в задании встретится раньше своего модуля — $in в фильтре {"sku": {"$in": [...]}}. Читается он ровно так, как выглядит: «значение поля есть в этом списке». Подробно — в главе 2.3.

Задача 1витрина каталога

Одна страница списка товаров

Нужна функция, которая возвращает страницу каталога и общее число товаров под фильтром. Это самая частая функция в любом магазине, и в ней собирается почти весь модуль 1.

Требованиячто должно работать
catalog_page(category=None, page=1, per_page=5,
             sort_field="price", desc=True) → (items, total)
Требованиячто должно работать
std::pair<std::vector<Product>, std::int64_t>
catalog_page(mongocxx::collection& products, const std::string& category,
             std::int64_t page, std::int64_t per_page,
             const std::string& sort_field = "price", bool desc = true);
Требованиячто должно работать
func catalogPage(category string, page, perPage int64,
    sortField string, desc bool) ([]Product, int64, error)
Требованиячто должно работать
catalog_page(category: nil, page: 1, per_page: 5,
             sort_field: "price", desc: true) → [items, total]
  1. Фильтр собирается, а не пишется целикомКатегория необязательна: без неё возвращается весь каталог. Фильтр — обычный словарь, поэтому он собирается по условию, а не склеивается строкой.
  2. Проекция обязательнаВ ответ идут только sku, title, price, rating и без _id. Массив stock и характеристики на странице списка не нужны — значит, их не запрашивают.
  3. Сортировка по двум ключамВторой ключ — sku. Без него товары с одинаковой ценой меняются местами между запросами, и один и тот же товар появляется на двух страницах подряд.
  4. total считает серверcount_documents(query) с тем же фильтром. Не len(list(...)): иначе ради одного числа на клиент приедет вся коллекция.
  5. Страница за пределами данных — пустой списокНе ошибка и не исключение. total при этом остаётся прежним.
solution.pyядро решения
def catalog_page(category=None, page=1, per_page=5,
                 sort_field="price", desc=True):
    query = {}
    if category:
        query["category"] = category

    total = products.count_documents(query)

    cursor = (products
              .find(query, {"_id": 0, "sku": 1, "title": 1,
                            "price": 1, "rating": 1})
              # второй ключ делает порядок однозначным
              .sort([(sort_field, -1 if desc else 1), ("sku", 1)])
              .skip((page - 1) * per_page)
              .limit(per_page))

    return list(cursor), total
solution.cppядро решения
bsoncxx::builder::basic::document query{};
if (!category.empty()) query.append(kvp("category", category));
auto filter = query.extract();

auto total = products.count_documents(filter.view());

mongocxx::options::find options;
options.projection(make_document(kvp("_id", 0), kvp("sku", 1),
    kvp("title", 1), kvp("price", 1), kvp("rating", 1)));
// второй ключ делает порядок однозначным
options.sort(make_document(kvp(sort_field, desc ? -1 : 1), kvp("sku", 1)));
options.skip((page - 1) * per_page);
options.limit(per_page);

std::vector<Product> items;
for (const auto& doc : products.find(filter.view(), options))
    items.push_back(to_product(doc));
return {items, total};
solution.goядро решения
query := bson.D{}
if category != "" {
    query = append(query, bson.E{Key: "category", Value: category})
}

total, err := products.CountDocuments(ctx, query)
if err != nil {
    return nil, 0, err
}

direction := 1
if desc {
    direction = -1
}

cursor, err := products.Find(ctx, query, options.Find().
    SetProjection(bson.D{{Key: "_id", Value: 0}, {Key: "sku", Value: 1},
        {Key: "title", Value: 1}, {Key: "price", Value: 1}, {Key: "rating", Value: 1}}).
    // второй ключ делает порядок однозначным
    SetSort(bson.D{{Key: sortField, Value: direction}, {Key: "sku", Value: 1}}).
    SetSkip((page - 1) * perPage).
    SetLimit(perPage))

var items []Product
return items, total, cursor.All(ctx, &items)
solution.rbядро решения
query = {}
query["category"] = category if category

total = PRODUCTS.count_documents(query)

# второй ключ делает порядок однозначным
order = { sort_field => desc ? -1 : 1, "sku" => 1 }

items = PRODUCTS
        .find(query, projection: { "_id" => 0, "sku" => 1, "title" => 1,
                                   "price" => 1, "rating" => 1 })
        .sort(order)
        .skip((page - 1) * per_page)
        .limit(per_page)
        .to_a

[items, total]
Вывододинаков во всех четырёх решениях
Ноутбуки, страница 1 из 2 (всего 5):
   114990 ₽  Ноутбук Apple MacBook Air 13     рейтинг 4.1
    89990 ₽  Ноутбук Acer Nitro V15           рейтинг 4.6
    67900 ₽  Ноутбук HP Pavilion 14           рейтинг 4.5

Страница 2:
    51490 ₽  Ноутбук ASUS VivoBook 15         рейтинг 4.3
    42990 ₽  Ноутбук Lenovo IdeaPad 3         рейтинг 4.2

Страница 9 из существующих 2: найдено 0 товаров, total по-прежнему 5
На полях: почему total считают отдельным запросом

Кажется избыточным — два обращения вместо одного. Но альтернатива хуже: чтобы узнать общее число из самой выборки, её пришлось бы получить целиком, без limit. На каталоге из 21 товара разницы нет, на 200 000 — это разница между 4 миллисекундами и минутой.

Задача 2приёмка поставки

Скрипт, который принимает поставку

Приехала машина. Нужно обновить остатки, завести новые позиции, снять с продажи то, что больше не возим, и напечатать отчёт. Работаем в песочнице.

ШагЧто делаемЧемЧто проверяем
1Прибавить пришедшее к остаткам трёх артикулов$incmatched_count: артикул мог не найтись
2Завести две новые позицииinsert_manyповтор не создаёт дублей
3Снять с продажи SKU-CP-018delete_manyсначала посчитать и посмотреть
4Бесплатная доставка всем аксессуарамupdate_manymodified_count совпал с ожиданием

Почему $inc, а не «прочитать и записать»

Соблазн понятный: прочитать текущий остаток, прибавить в Python, записать обратно. Так делать нельзя, и вот почему: пока вы читаете, вторая приёмка на соседнем складе делает то же самое. Оба прочитали 10, оба записали 15 — а приехало 5 и 5, должно быть 20. Пять штук исчезли, и никакая ошибка в логах об этом не скажет.

solution.pyостатки
for sku, qty in ARRIVED.items():
    result = box.update_one({"sku": sku}, {"$inc": {"qty_total": qty}})
    if result.matched_count == 0:
        print(f"  ВНИМАНИЕ: артикул {sku} не найден в каталоге")
    updated += result.modified_count
solution.cppостатки
for (const auto& [sku, qty] : arrived) {
    auto res = box.update_one(
        make_document(kvp("sku", sku)),
        make_document(kvp("$inc", make_document(kvp("qty_total", qty)))));

    if (res->matched_count() == 0)
        std::cout << "  ВНИМАНИЕ: артикул " << sku << " не найден" << std::endl;
    updated += res->modified_count();
}
solution.goостатки
for _, item := range arrived {
    res, err := box.UpdateOne(ctx,
        bson.D{{Key: "sku", Value: item.SKU}},
        bson.D{{Key: "$inc", Value: bson.D{{Key: "qty_total", Value: item.Qty}}}})
    if err != nil {
        log.Fatal(err)
    }
    if res.MatchedCount == 0 {
        fmt.Printf("  ВНИМАНИЕ: артикул %s не найден\n", item.SKU)
    }
    updated += res.ModifiedCount
}
solution.rbостатки
ARRIVED.each do |sku, qty|
  result = BOX.update_one({ "sku" => sku },
                            { "$inc" => { "qty_total" => qty } })
  puts "  ВНИМАНИЕ: артикул #{sku} не найден" if result.matched_count.zero?
  updated += result.modified_count
end

Повторный запуск не должен ломать данные

Скрипт приёмки запускают повторно чаще, чем кажется: упала сеть, оператор нажал дважды, задача перезапустилась. Поэтому у новых позиций задан свой _id — вторая попытка упрётся в E11000, а не создаст копии. Ключ ordered=False позволяет вставить всё, что можно, а про остальное честно сообщить.

solution.pyновинки
try:
    result = box.insert_many(NEW_ITEMS, ordered=False)
    report["новых позиций"] = len(result.inserted_ids)
except BulkWriteError as error:
    already = len(error.details["writeErrors"])
    report["новых позиций"] = len(NEW_ITEMS) - already
    print(f"  {already} позиций уже были в каталоге — пропущены")
solution.cppновинки
mongocxx::options::insert insert_options;
insert_options.ordered(false);

try {
    auto many = box.insert_many(new_items, insert_options);
    report.emplace_back("новых позиций", many->inserted_count());
} catch (const mongocxx::bulk_write_exception& error) {
    // список неудач лежит в raw_server_error()["writeErrors"]
    auto already = count_write_errors(error);
    std::cout << "  " << already << " позиций уже были в каталоге" << std::endl;
    report.emplace_back("новых позиций", new_items.size() - already);
}
solution.goновинки
many, err := box.InsertMany(ctx, newItems,
    options.InsertMany().SetOrdered(false))

switch {
case err == nil:
    report = append(report, [2]any{"новых позиций", len(many.InsertedIDs)})
default:
    var bulkErr mongo.BulkWriteException
    if !errors.As(err, &bulkErr) {
        log.Fatal(err)
    }
    already := len(bulkErr.WriteErrors)
    fmt.Printf("  %d позиций уже были в каталоге\n", already)
    report = append(report, [2]any{"новых позиций", len(newItems) - already})
}
solution.rbновинки
begin
  result = BOX.insert_many(NEW_ITEMS, ordered: false)
  report["новых позиций"] = result.inserted_count
rescue Mongo::Error::BulkWriteError => error
  # частичный отказ пачки — именно BulkWriteError, а не OperationFailure
  already = error.result["writeErrors"].size
  report["новых позиций"] = NEW_ITEMS.size - already
  puts "  #{already} позиций уже были в каталоге — пропущены"
end

Перед удалением — посмотреть

Три строки, которые отделяют рабочий скрипт от опасного: посчитать, показать, удалить. На приёмке эти строки спрашивают отдельно.

solution.pyснятие с продажи
query = {"sku": {"$in": DISCONTINUED}}
to_remove = box.count_documents(query)
print(f"  под снятие с продажи попало: {to_remove}")
for doc in box.find(query, {"_id": 1, "title": 1, "price": 1}):
    print(f"    {doc}")
report["снято с продажи"] = box.delete_many(query).deleted_count
solution.cppснятие с продажи
auto query = make_document(kvp("sku",
    make_document(kvp("$in", make_array("SKU-CP-018")))));

std::cout << "  под снятие попало: "
          << box.count_documents(query.view()) << std::endl;

mongocxx::options::find preview;
preview.projection(make_document(kvp("_id", 1), kvp("title", 1), kvp("price", 1)));
for (const auto& doc : box.find(query.view(), preview))
    std::cout << "    " << bsoncxx::to_json(doc) << std::endl;

report.emplace_back("снято с продажи",
    box.delete_many(query.view())->deleted_count());
solution.goснятие с продажи
query := bson.D{{Key: "sku",
    Value: bson.D{{Key: "$in", Value: discontinued}}}}

toRemove, _ := box.CountDocuments(ctx, query)
fmt.Printf("\n  под снятие с продажи попало: %d\n", toRemove)

cursor, _ := box.Find(ctx, query, options.Find().SetProjection(
    bson.D{{Key: "_id", Value: 1}, {Key: "title", Value: 1}, {Key: "price", Value: 1}}))
var doomed []bson.M
cursor.All(ctx, &doomed)
for _, doc := range doomed {
    fmt.Printf("    %v\n", doc)
}

deleted, _ := box.DeleteMany(ctx, query)
solution.rbснятие с продажи
query = { "sku" => { "$in" => DISCONTINUED } }
puts "  под снятие с продажи попало: #{BOX.count_documents(query)}"

BOX.find(query, projection: { "_id" => 1, "title" => 1, "price" => 1 })
   .each { |doc| puts "    #{doc.inspect}" }

report["снято с продажи"] = BOX.delete_many(query).deleted_count
Выводотчёт о приёмке на стенде
  под снятие с продажи попало: 1
    {'_id': 'p-018', 'title': 'Блок питания be quiet! 650 Вт', 'price': 8490}

  бесплатная доставка коснётся: 5 товаров

Отчёт о приёмке:
  остатки обновлены        3
  новых позиций            2
  снято с продажи          1
  бесплатная доставка      5
  итого в каталоге         22
Выводотчёт о приёмке на стенде
  под снятие с продажи попало: 1
    { "_id" : "p-018", "title" : "Блок питания be quiet! 650 Вт", "price" : 8490 }

  бесплатная доставка коснётся: 5 товаров

Отчёт о приёмке:
  остатки обновлены        3
  новых позиций            2
  снято с продажи          1
  бесплатная доставка      5
  итого в каталоге         22
Выводотчёт о приёмке на стенде
  под снятие с продажи попало: 1
    {"title":"Блок питания be quiet! 650 Вт","price":{"$numberInt":"8490"},"_id":"p-018"}

  бесплатная доставка коснётся: 5 товаров

Отчёт о приёмке:
  остатки обновлены        3
  новых позиций            2
  снято с продажи          1
  бесплатная доставка      5
  итого в каталоге         22
Выводотчёт о приёмке на стенде
  под снятие с продажи попало: 1
    {"_id"=>"p-018", "title"=>"Блок питания be quiet! 650 Вт", "price"=>8490}

  бесплатная доставка коснётся: 5 товаров

Отчёт о приёмке:
  остатки обновлены        3
  новых позиций            2
  снято с продажи          1
  бесплатная доставка      5
  итого в каталоге         22

§2Самопроверка

Скрипт check.py проверяет не ваш код, а результат — состояние базы. Так же работает и приёмка: смотрят на данные, а не на текст программы.

python3 check.pyрезультат на стенде
Задача 1 · витрина каталога
 ✓ товаров в каталоге shop.products               21
 ✓ ноутбуков                                      5
 ✓ самый дорогой товар                            'Ноутбук Apple MacBook Air 13'

Задача 2 · приёмка поставки
 ✓ всего позиций в песочнице                      22
 ✓ новинка p-101 заведена                         1
 ✓ новинка p-102 заведена                         1
 ✓ снятый с продажи SKU-CP-018 удалён             0
 ✓ бесплатная доставка у аксессуаров              5
 ✓ остаток по SKU-NB-001                          5
 ✓ остаток по SKU-PH-006                          12
 ✓ остаток по SKU-PR-010                          3
 ✓ эталонная база shop не тронута                 0

Пройдено 12 из 12

Последняя проверка — главная. Она смотрит, не появилось ли поле free_shipping в эталонной базе shop. Если появилось, значит, правки ушли не туда: в базе курса это лечится одной командой, в рабочей — разговором с руководителем.

§3Где писать решение и как сдать

Решения в папке practice01 — эталонные: они показывают, к чему прийти. Своё решение вы пишете в собственном репозитории на GitHub и сдаёте ссылкой на него.

mongodb-practice/

practice01/задание и check.py — отсюда запускается проверка
stend/данные и загрузка
work/ваш репозиторий
work/practice01/ваше решение и README.md с числами
  1. Заведите свой репозиторийНа GitHub: New repository, имя вроде mongodb-ivanov, без README. Один репозиторий на все работы модуля.
  2. Склонируйте его в папку workВнутри mongodb-practice: git clone https://github.com/<ваш-логин>/mongodb-ivanov.git work. Папка work исключена из репозитория практик — ваши файлы в него не попадут, а стенд и Docker видят их так же, как всё остальное.
  3. Напишите решениеСоздайте папку work/practice01 и файл solution.pysolution.cppsolution.gosolution.rb. Эталон из practice01 открывайте после своей попытки. Для Go один раз в папке work: go mod init mongodb-work и go get go.mongodb.org/mongo-driver/v2/mongo.
  4. Проверьте на чистых данныхСброс баз, запуск решения, check.py — команды ниже. Должно получиться «Пройдено 12 из 12».
  5. Опишите результатРядом с решением — README.md: как запустить и какие числа получились. Пяти строк достаточно.
  6. Отправьте и пришлите ссылкуИз папки work: git add practice01, git commit -m "ПР-01: каталог товаров", git push. Преподавателю — ссылку на репозиторий.
Форма Create a new repository с именем mongodb-ivanov
GitHub · Create a new repository — имя mongodb-ivanov, без README и .gitignore, затем Create repository
Терминалваше решение: из корня mongodb-practice
docker compose run --rm reset
docker compose run --rm python work/practice01/solution.py
docker compose run --rm python practice01/check.py        # Пройдено 12 из 12
Терминалваше решение: из корня mongodb-practice
docker compose run --rm reset
docker compose run --rm cpp work/practice01/solution.cpp
docker compose run --rm python practice01/check.py        # Пройдено 12 из 12
Терминалваше решение: из корня mongodb-practice
docker compose run --rm reset
docker compose run --rm go work/practice01/solution.go
docker compose run --rm python practice01/check.py        # Пройдено 12 из 12
Терминалваше решение: из корня mongodb-practice
docker compose run --rm reset
docker compose run --rm ruby work/practice01/solution.rb
docker compose run --rm python practice01/check.py        # Пройдено 12 из 12
VS Code на macOS: <code>work/practice01/solution.py</code>, запуск и <code>check.py</code> — «Пройдено 12 из 12»
VS Code · macOS · work/practice01/solution.py, запуск и check.py — «Пройдено 12 из 12» (в кадре вместо вашего решения — эталонное)
VS Code на macOS: <code>work/practice01/solution.cpp</code>, запуск и <code>check.py</code> — «Пройдено 12 из 12»
VS Code · macOS · work/practice01/solution.cpp, запуск и check.py — «Пройдено 12 из 12» (в кадре вместо вашего решения — эталонное)
VS Code на macOS: <code>work/practice01/solution.go</code>, запуск и <code>check.py</code> — «Пройдено 12 из 12»
VS Code · macOS · work/practice01/solution.go, запуск и check.py — «Пройдено 12 из 12» (в кадре вместо вашего решения — эталонное)
VS Code на macOS: <code>work/practice01/solution.rb</code>, запуск и <code>check.py</code> — «Пройдено 12 из 12»
VS Code · macOS · work/practice01/solution.rb, запуск и check.py — «Пройдено 12 из 12» (в кадре вместо вашего решения — эталонное)

Без Docker команды те же, что в начале страницы, только решение запускается из своей папки: cd work/practice01, а проверка — из practice01. Скриншот вывода check.py прикладывать не нужно: на приёмке решение запускается заново на свежих данных.

Репозиторий mongodb-ivanov на GitHub с папкой practice01 после git push
GitHub · репозиторий после git push: папка practice01 с решением и README внутри неё
Что спросят на приёмке

Почему сортировка по двум ключам, а не по одному. Что вернёт функция для девятой страницы. Почему остатки меняются через $inc. Что произойдёт при повторном запуске скрипта приёмки. Ответ «так было в примере» не принимается — на эти четыре вопроса и написана работа.

Дальше

Следующий шаг — контрольная точка: та же предметная область, но без разбора и с отчётом, который читает человек, не заглядывающий в базу.

Практическая работа 01 · Справочник по MongoDB Автор: Макаров Максим Николаевич