Несмотря на огромное число разнообразных сайтов, практически всю веб-разработку можно свести к CRUD-операциям. В этом уроке мы познакомимся с ними подробнее.
Что такое CRUD
CRUD — это широко распространенный термин, означающий четыре стандартные операции над любой сущностью. Рассмотрим такой пример для статей:
- Создание (Create) — добавление новой статьи.
- Чтение (Read) — просмотр статьи пользователями сайта или в административном интерфейсе.
- Обновление (Update) — редактирование содержимого статьи, изменение заголовка или категории.
- Удаление (Delete) — удаление статьи из базы данных.
Точно так же можно расписать действия над любыми другими ресурсами: комментариями к статьям, тегами, авторами и так далее. Чтобы создать полный CRUD для статей, нужно выполнить следующие действия:
- Создать сущность в коде (как правило, это класс).
- Добавить таблицу в базу данных для хранения статей.
- Написать тесты для проверки обработчиков статей.
- Добавить обработчики для выполнения CRUD операций.
- Добавить шаблоны для отображения статей на сайте.
Ниже мы пройдемся по всему процессу создания CRUD статьи (сущность Article).
Начнем с роутинга. Полный CRUD для статей включает минимум семь маршрутов:
Такое соглашение об именовании маршрутов изначально появилось в Ruby On Rails, а затем его адаптировали во многих других.
Ресурсный роутинг — это механизм в Ruby on Rails, который позволяет легко создавать маршруты для стандартных операций CRUD (Создание, Чтение, Обновление, Удаление) для ресурсов, таких как модели. Он значительно упрощает процесс определения маршрутов, так как автоматически создает все необходимые маршруты для работы с ресурсами.
Командой bin/rails g resource мы можем создать новый ресурс и полный набор файлов и кода, необходимых для работы с ресурсом. Давайте разберем, что именно делает эта команда и какие файлы она создает.
- bin/rails: Это исполняемый файл Rails, который запускает команды в вашем приложении.
- g: Сокращение от "generate", команда для генерации кода.
- resource: Указывает, что мы хотитим создать ресурс, который будет включать в себя маршруты, контроллер, представления и миграции.
- article: Имя ресурса, в данном случае это Article.
- title:string body:text: Это атрибуты, которые будут добавлены к модели Article. Здесь title будет строковым полем, а body — текстовым полем.
При выполнении этой команды Rails создаст следующие файлы и изменения:
-
Миграция: Создается файл миграции в db/migrate, который будет содержать код для создания таблицы articles с полями title и body.
Пример миграции:
-
Модель: Создается файл модели app/models/article.rb, который будет представлять сущность Article.
Пример модели:
-
Контроллер: Создается контроллер app/controllers/articles_controller.rb, который будет обрабатывать запросы, связанные с ресурсом Article.
-
Представления: Создается директория app/views/articles, в которой будут находиться шаблоны представлений для действий контроллера (index, show, new, edit).
-
Маршруты: В файл config/routes.rb будет добавлена строка для ресурсного роутинга:
Команда генерации упрощает процесс создания нового ресурса в приложении Ruby on Rails, автоматически генерируя все необходимые компоненты для работы с сущностью. После выполнения этой команды останется только выполнить миграцию базы данных с помощью bin/rails db:migrate и начать использовать созданный ресурс.
Модель была создана пустой, мы можем добавить в нее валидацию присутствия полей:
После генерации ресурса Article с помощью команды bin/rails g resource articles title:string body:text, у нас есть все необходимые компоненты для работы с CRUD операциями. Давайте подробнее рассмотрим каждую из них.
Создание (Create)
Создание новой статьи начинается с отображения формы для ввода данных. Когда пользователь заполняет форму и отправляет ее, данные передаются на сервер с помощью POST-запроса.
Пример метода create в контроллере:
В этом методе мы создаем новый объект Article, используя параметры, полученные из формы с помощью Strong Params. Strong Parameters — это механизм в Ruby on Rails, который помогает защитить приложение от уязвимостей, связанных с массовым присвоением (mass assignment). Он позволяет явно указывать, какие параметры могут быть использованы для создания или обновления объектов, что предотвращает возможность изменения нежелательных атрибутов.
Форма для создания новой статьи отображается на странице, когда пользователь переходит по маршруту GET /articles/new. В контроллере ArticlesController метод new отвечает за инициализацию нового объекта Article и отображение формы.
После выполнения метода new(), Rails автоматически рендерит шаблон app/views/articles/new.html.erb, который содержит форму для создания новой статьи:
Здесь Используется хелпер form_with, который создает форму для объекта @article. Если при валидации возникли ошибки, они будут отображены в верхней части формы, что позволяет пользователю увидеть, что нужно исправить. Поля формы (title и body) автоматически связываются с атрибутами объекта @article.
Если сохранение прошло успешно, происходит перенаправление на страницу статьи. В противном случае отображается форма с ошибками.
Чтение (Read)
Чтение статей включает в себя два действия: отображение списка всех статей и просмотр конкретной статьи.
Пример метода index:
Переменная @articles будет доступна в представлении, что позволяет отобразить список статей.
Пример метода show:
Метод index получает все статьи из базы данных и передает их в представление. Метод show находит конкретную статью по ID и также передает ее в представление.
Обновление (Update)
Обновление статьи начинается с отображения формы редактирования, которая заполняется текущими данными статьи. После внесения изменений и отправки формы данные передаются на сервер с помощью PATCH или PUT-запроса.
Пример метода update:
В этом методе мы находим статью по ID и обновляем ее с новыми параметрами. Если обновление прошло успешно, происходит перенаправление на страницу статьи. В противном случае отображается форма редактирования с ошибками.
Если при валидации возникли ошибки, они будут отображены в верхней части формы, что позволяет пользователю увидеть, что нужно исправить.
Поля формы (title и body) автоматически заполняются текущими значениями статьи.
Удаление (Delete)
Удаление статьи происходит по запросу DELETE. Обычно это действие инициируется пользователем через кнопку "Удалить" на странице статьи или в списке статей.
Пример метода destroy:
В этом методе мы находим статью по ID и вызываем метод destroy, чтобы удалить ее из базы данных. После успешного удаления происходит перенаправление на страницу со списком статей.
Чтобы выполнить запрос на удаление ресурса из шаблона, мы должны использовать хелпер link_to с указанием метода :delete. Это позволяет отправить DELETE-запрос на сервер, когда пользователь нажимает на ссылку.
Flash
Сообщения flash используются для отображения временных уведомлений пользователям, таких как сообщения об успехе или ошибках после отправки форм или выполнения других действий. Флеш-сообщения передаются в шаблон и там выводятся. После их извлечения хранилище обнуляется.
Флеш-сообщения показываются только на один запрос. После обновления страницы или перехода в другое место они пропадают. Это удобно, так как не нужно следить за их жизненным циклом.
Во многих веб-фреймворках, включая Ruby on Rails, типы флеш-сообщений часто стандартизированы, например:
-
notice или success: для информирования пользователя о том, что действие было выполнено успешно.
-
error для отображения ошибок, связанных с валидацией или другими проблемами. Например, сообщение о том, что введённые данные некорректны.
-
info: Используется для предоставления дополнительной информации пользователю, которая не является ни успешной, ни ошибочной. Например, сообщение о том, что действие будет выполнено позже.
-
warning: для предупреждения пользователя о потенциальных проблемах или рисках, связанных с его действиями.
Пример использования Flash
В контроллере Ruby on Rails мы можем использовать эти типы сообщений следующим образом:
Чтобы вывести flash-сообщения в нашем приложении на Ruby on Rails, добавим соответствующий код в представление. Обычно это делается в основном макете приложения, чтобы сообщения отображались на всех страницах:
app/views/layouts/application.html.erb:
Собираем все вместе
Итоговый контроллер: Полный разбор элементов CRUD
Собираем все элементы CRUD в итоговом контроллере ArticlesController, который управляет статьями в нашем приложении. Контроллер включает следующие действия:
-
create: Создает новую статью, проверяет наличие ошибок и перенаправляет пользователя на страницу статьи или возвращает форму с сообщением об ошибках.
-
new: Инициализирует новый объект статьи для отображения формы создания.
-
index: Получает все статьи и отображает их в списке.
-
show: Находит и отображает конкретную статью по ее идентификатору.
-
edit: Находит статью для редактирования и отображает форму.
-
update: Обновляет существующую статью, проверяет наличие ошибок и перенаправляет пользователя или возвращает форму с сообщениями об ошибках.
-
destroy: Удаляет статью и перенаправляет пользователя на список статей или возвращает сообщение об ошибке, если удаление не удалось.
Контроллер также включает метод article_params, который использует Strong Parameters для обеспечения безопасности, разрешая только определенные параметры для массового присвоения.
Обработка HTTP-ошибок
Обработка HTTP-ошибок позволяет обеспечить пользователям понятные и информативные сообщения об ошибках. В Ruby on Rails существует несколько способов обработки ошибок, которые позволяют разработчикам управлять различными ситуациями, такими как отсутствие запрашиваемых ресурсов, ошибки валидации и внутренние серверные ошибки.
Рассмотрим, как настроить маршруты для обработки ошибок, создать контроллер для управления этими ошибками и использовать механизмы Rails для перенаправления пользователей на соответствующие страницы ошибок.
Настройка обработки ошибок
-
В Ruby on Rails настройка config.exceptions_app = routes в config/application.rb позволяет нам обрабатывать ошибки через маршруты приложения, что дает возможность создавать свои страницы ошибок.
В файле config/application.rb добавим следующую строку:
Это указывает фреймворку использовать маршруты приложения для обработки исключений.
-
В файле config/routes.rb добавим маршруты для обработки ошибок:
-
Создадим контроллер ErrorsController в соответствующем файле app/controllers/errors_controller.rb:
-
Создадим директорию app/views/errors и добавим в нее шаблоны для ошибок:
app/views/errors/not_found.html.erb:
app/views/errors/server_error.html.erb:
Поведение вывода ошибок зависит от конфигурации окружения, в котором работает приложение. Если config.consider_all_requests_local установлено в true (что является значением по умолчанию в development-среде), Rails будет показывать полные трассировки стека и подробные сообщения об ошибках в браузере. Это позволяет видеть, что пошло не так, и быстро исправлять ошибки.
В production-среде consider_all_requests_local обычно установлено в false. Rails не будет показывать полные трассировки стека. Вместо этого фреймворк будет отображать стандартные страницы ошибок (например, 404 или 500) или наши страницы ошибок.
Изменить поведение вывода ошибок в разных окружениях, можно изменив config/environments/.rb* файлы. Например, в config/environments/development.rb:
Теперь наше приложение настроено для обработки ошибок через маршруты, что позволяет предоставлять пользователям более информативные и стилизованные страницы ошибок. Это улучшает взаимодействие с приложением и делает его более удобным для пользователей. Мы можем дополнительно настроить стили и содержание этих страниц в зависимости от требований и дизайна приложения.
Заключение
Мы изучили создания CRUD-сущности в Ruby on Rails, мы рассмотрели ключевые этапы, включая настройку ресурсного роутинга, создание контроллеров и представлений, а также обработку HTTP-ошибок. Эти аспекты обеспечивают функциональность, удобство использования и надежность веб-приложения.
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 17:18:01 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="IFRRzcNqK6XhmEguCLfeVy6D86N5GX9PTeL46Wh9GODPhZr6MRSGxVfbbLYEuC4g7oreCXEuge3wAmK9Onr_jg";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>CRUD | Ruby On Rails</title>
<meta name="description" content="CRUD / Ruby On Rails: Учимся создавать CRUD для сущности">
<link rel="canonical" href="https://ru.hexlet.io/courses/rails-basics/lessons/crud/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="CRUD">
<meta property="og:title" content="Ruby On Rails">
<meta property="og:description" content="CRUD / Ruby On Rails: Учимся создавать CRUD для сущности">
<meta property="og:url" content="https://ru.hexlet.io/courses/rails-basics/lessons/crud/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="BScit-Ka42z312lH7vZEr15_XIuOsy810mJ3ewjmUVbq9umAEORODEGUTd_i-bTYnnZxIYaE0Zdvgu0vWuG2OA" />
<script src="/vite/assets/inertia-INZxX8jp.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-6pOtQ3OW.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTExODcsInB1ciI6ImJsb2JfaWQifX0=--21b225d5b1f2f885f46e9ef4e79641fbea2f76dc/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Coding%20workshop-bro.webp"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T17:18:01.311Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"mgva7Dj5f7gUPLYXlmyT2WgMYltFtnbrqLK9ybUJJQx12hHbyofS2KJ_ko-aY2OuqAVP8U2BiEkVUied5w7CYg","topics":[{"id":88659,"title":"Добрый день, \n\"It works on my machine\" - у меня все тесты прошли, а в пайплане падает, я не очень понимаю суть ошибки строчки 29-34, если я правильно всё понял.\n\nhttps://github.com/PeterChebanov/hexlet-assignments/actions/runs/5149244540/jobs/9272001444\n","plain_title":"Добрый день, \"It works on my machine\" - у меня все тесты прошли, а в пайплане падает, я не очень понимаю суть ошибки строчки 29-34, если я правильно всё понял. https://github.com/PeterChebanov/hexlet-assignments/actions/runs/5149244540/jobs/9272001444 ","creator":{"public_name":"Petr Che","id":147023,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":177076,"body":"Петр, добрый день.\n\nВ данном задании тесты проверяют в том числе ваши тесты. Ошибка здесь плавающая (особенность запуска тестов в рельсах - порядок тестов случайный). Попробуйте позапускать тесты несколько раз и проверить вывод локально\n\n```\nError:\nTasksControllerTest#test_should_update_task:\nRuntimeError: Neutered Exception ActionView::MissingTemplate: Missing partial tasks/_task with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :jbuilder]}.\n\nSearched in:\n * \"/project/course/crud/assignment/app/views\"\n * \"/project/vendor/bundle/ruby/3.2.0/gems/turbo-rails-1.4.0/app/views\"\n * \"/project/vendor/bundle/ruby/3.2.0/gems/actiontext-7.0.5/app/views\"\n * \"/project/vendor/bundle/ruby/3.2.0/gems/actionmailbox-7.0.5/app/views\"\n\n app/controllers/tasks_controller.rb:35:in `update'\n test/controllers/tasks_controller_test.rb:47:in `block in <class:TasksControllerTest>'\n```","topic_id":88659}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"CRUD","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":4258,"slug":"rails_basics_crud_exercise","name":null,"state":"active","kind":"exercise","language":"ruby","locale":"ru","has_web_view":true,"has_test_view":false,"reviewable":true,"readme":"Создайте CRUD для сущности Task и напишите тесты.\n\n## Ссылки\n\n* [CRUD, глаголы и экшены](https://edgeguides.rubyonrails.org/routing.html#crud-verbs-and-actions)\n* [Faker](https://github.com/faker-ruby/faker)\n\n## Задачи\n\nУ сущности Задача есть следующие поля:\n\n* `name` - обязательное, строка. Название задачи\n* `description` - необязательное. Описание задачи\n* `status` - обязательное, строка. По умолчанию задача создается в новом статусе\n* `creator` - обязательно, строка. Создатель задачи\n* `performer` - необязательное, строка. Тот на кого поставлена задача\n* `completed` - обязательное булево значение\n\nСгенерируйте базовый код с помощью команды\n\n ```bash\n bin/rails generate resource task # Допишите необходимые атрибуты и их типы\n ```\n\nДля генерации задач (в тестах в том числе) используйте Faker\n\n### app/models/task.rb\n\nДобавьте необходимую валидацию в модель.\n\n### app/views/layouts/application.html.erb\n\nДобавьте в навигацию ссылку на список задач.\n\n### app/controllers/tasks_controller.rb\n\nРеализуйте методы CRUD.\n\nДобавьте вывод flash-сообщений.\n\n### app/views/tasks\n\nСоздайте шаблоны для просмотра, создания и редактирования задач.\n\n### test/controllers/tasks_controller_test.rb\n\nНапишите тесты для методов CRUD.\n","prepared_readme":"Создайте CRUD для сущности Task и напишите тесты.\n\n## Ссылки\n\n* [CRUD, глаголы и экшены](https://edgeguides.rubyonrails.org/routing.html#crud-verbs-and-actions)\n* [Faker](https://github.com/faker-ruby/faker)\n\n## Задачи\n\nУ сущности Задача есть следующие поля:\n\n* `name` - обязательное, строка. Название задачи\n* `description` - необязательное. Описание задачи\n* `status` - обязательное, строка. По умолчанию задача создается в новом статусе\n* `creator` - обязательно, строка. Создатель задачи\n* `performer` - необязательное, строка. Тот на кого поставлена задача\n* `completed` - обязательное булево значение\n\nСгенерируйте базовый код с помощью команды\n\n ```bash\n bin/rails generate resource task # Допишите необходимые атрибуты и их типы\n ```\n\nДля генерации задач (в тестах в том числе) используйте Faker\n\n### app/models/task.rb\n\nДобавьте необходимую валидацию в модель.\n\n### app/views/layouts/application.html.erb\n\nДобавьте в навигацию ссылку на список задач.\n\n### app/controllers/tasks_controller.rb\n\nРеализуйте методы CRUD.\n\nДобавьте вывод flash-сообщений.\n\n### app/views/tasks\n\nСоздайте шаблоны для просмотра, создания и редактирования задач.\n\n### test/controllers/tasks_controller_test.rb\n\nНапишите тесты для методов CRUD.\n","has_solution":true,"entity_name":"CRUD"},"units":[{"id":4620,"name":"theory","url":"/courses/rails-basics/lessons/crud/theory_unit"},{"id":13440,"name":"quiz","url":"/courses/rails-basics/lessons/crud/quiz_unit"},{"id":14497,"name":"exercise","url":"/courses/rails-basics/lessons/crud/exercise_unit"}],"links":[{"id":424236,"name":"Getting Started with Rails - CRUD","url":"https://guides.rubyonrails.org/getting_started.html#crud-actions\n"},{"id":424237,"name":"Rails Guides - Rails Routing from the Outside In","url":"https://guides.rubyonrails.org/routing.html\n"},{"id":424238,"name":"Как использовать faker для заполнения таблиц в Rails?","url":"https://ru.hexlet.io/qna/ruby/questions/kak-ispolzovat-faker-dlya-zapolneniya-tablits-v-rails\n"},{"id":424239,"name":"Faker","url":"https://github.com/faker-ruby/faker"},{"id":424240,"name":"CRUD глаголы и экшены","url":"https://edgeguides.rubyonrails.org/routing.html#crud-verbs-and-actions"}],"ordered_units":[{"id":4620,"name":"theory","url":"/courses/rails-basics/lessons/crud/theory_unit"},{"id":13440,"name":"quiz","url":"/courses/rails-basics/lessons/crud/quiz_unit"},{"id":14497,"name":"exercise","url":"/courses/rails-basics/lessons/crud/exercise_unit"}],"id":2071,"slug":"crud","state":"approved","name":"CRUD","course_order":450,"goal":"Учимся создавать CRUD для сущности","self_study":"Создайте новый Rails-проект для экспериментов, если его еще нет.\n\n1. Повторите примеры из теории. С помощью генераторов создайте CRUD для сущности *Article*. У сущности должны быть следующие поля\n * *title* - текстовая строка, минимум 10 символов\n * *body* - текст. Минимум 200 символов\n * *author* - текстовая строка, имя автора\n * *published?* - поле-флаг с признаком публикации\n\n2. На главную страницу добавьте ссылку на список статей.\n\n\n","theory_video_provider":"vimeo","theory_video_uid":"582087857","theory":"Несмотря на огромное число разнообразных сайтов, практически всю веб-разработку можно свести к [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциям. В этом уроке мы познакомимся с ними подробнее.\n\n## Что такое CRUD\n\nCRUD — это широко распространенный термин, означающий четыре стандартные операции над любой сущностью. Рассмотрим такой пример для статей:\n\n- Создание (*Create*) — добавление новой статьи.\n- Чтение (*Read*) — просмотр статьи пользователями сайта или в административном интерфейсе.\n- Обновление (*Update*) — редактирование содержимого статьи, изменение заголовка или категории.\n- Удаление (*Delete*) — удаление статьи из базы данных.\n\nТочно так же можно расписать действия над любыми другими ресурсами: комментариями к статьям, тегами, авторами и так далее. Чтобы создать полный CRUD для статей, нужно выполнить следующие действия:\n\n- Создать сущность в коде (как правило, это класс).\n- Добавить таблицу в базу данных для хранения статей.\n- Написать тесты для проверки обработчиков статей.\n- Добавить обработчики для выполнения CRUD операций.\n- Добавить шаблоны для отображения статей на сайте.\n\nНиже мы пройдемся по всему процессу создания CRUD статьи (сущность *Article*).\n\nНачнем с **роутинга**. Полный CRUD для статей включает минимум семь маршрутов:\n\n| Метод | Маршрут | Шаблон | Описание |\n| --------- | -------------------- | ------------------------ | ---------------------------------- |\n| GET | /articles | articles/index.html.erb | Список статей |\n| GET | /articles/{id} | articles/show.html.erb | Просмотр статьи |\n| GET | /articles/new | articles/new.html.erb | Форма создания новой статьи |\n| POST | /articles | | Создание новой статьи |\n| GET | /articles/{id}/edit | articles/edit.html.erb | Форма редактирования статьи |\n| PATCH/PUT | /articles/{id} | | Обновление статьи |\n| DELETE | /articles/{id} | | Удаление статьи |\n\nТакое соглашение об именовании маршрутов изначально появилось в Ruby On Rails, а затем его адаптировали во многих других.\n\nРесурсный роутинг — это механизм в Ruby on Rails, который позволяет легко создавать маршруты для стандартных операций CRUD (Создание, Чтение, Обновление, Удаление) для ресурсов, таких как модели. Он значительно упрощает процесс определения маршрутов, так как автоматически создает все необходимые маршруты для работы с ресурсами.\n\nКомандой `bin/rails g resource` мы можем создать новый ресурс и полный набор файлов и кода, необходимых для работы с ресурсом. Давайте разберем, что именно делает эта команда и какие файлы она создает.\n\n```bash\nbin/rails g resource article title:string body:text\n invoke active_record\n create db/migrate/20250116162806_create_articles.rb\n create app/models/article.rb\n invoke test_unit\n create test/models/article_test.rb\n create test/fixtures/articles.yml\n invoke controller\n create app/controllers/articles_controller.rb\n invoke erb\n create app/views/articles\n invoke test_unit\n create test/controllers/articles_controller_test.rb\n invoke helper\n create app/helpers/articles_helper.rb\n invoke test_unit\n invoke resource_route\n route resources :articles\n```\n- `bin/rails`: Это исполняемый файл Rails, который запускает команды в вашем приложении.\n- `g`: Сокращение от \"generate\", команда для генерации кода.\n- `resource`: Указывает, что мы хотитим создать ресурс, который будет включать в себя маршруты, контроллер, представления и миграции.\n- `article`: Имя ресурса, в данном случае это `Article`.\n- `title:string body:text`: Это атрибуты, которые будут добавлены к модели `Article`. Здесь `title` будет строковым полем, а `body` — текстовым полем.\n\nПри выполнении этой команды Rails создаст следующие файлы и изменения:\n\n1. **Миграция**: Создается файл миграции в `db/migrate`, который будет содержать код для создания таблицы `articles` с полями `title` и `body`.\n\n Пример миграции:\n ```ruby\n class CreateArticles < ActiveRecord::Migration[6.0]\n def change\n create_table :articles do |t|\n t.string :title\n t.text :body\n\n t.timestamps\n end\n end\n end\n ```\n\n2. **Модель**: Создается файл модели `app/models/article.rb`, который будет представлять сущность `Article`.\n\n Пример модели:\n ```ruby\n class Article < ApplicationRecord\n end\n ```\n\n3. **Контроллер**: Создается контроллер `app/controllers/articles_controller.rb`, который будет обрабатывать запросы, связанные с ресурсом `Article`.\n\n ```ruby\n class ArticlesController < ApplicationController\n end\n ```\n\n4. **Представления**: Создается директория *app/views/articles*, в которой будут находиться шаблоны представлений для действий контроллера (*index*, *show*, *new*, *edit*).\n\n5. **Маршруты**: В файл *config/routes.rb* будет добавлена строка для ресурсного роутинга:\n\n ```ruby\n resources :articles\n ```\n\nКоманда генерации упрощает процесс создания нового ресурса в приложении Ruby on Rails, автоматически генерируя все необходимые компоненты для работы с сущностью. После выполнения этой команды останется только выполнить миграцию базы данных с помощью `bin/rails db:migrate` и начать использовать созданный ресурс.\n\nМодель была создана пустой, мы можем добавить в нее валидацию присутствия полей:\n\n```ruby\nclass Article < ApplicationRecord\n validates :title, presence: true\n validates :body, presence: true\nend\n```\n\nПосле генерации ресурса `Article` с помощью команды `bin/rails g resource articles title:string body:text`, у нас есть все необходимые компоненты для работы с CRUD операциями. Давайте подробнее рассмотрим каждую из них.\n\n### Создание (Create)\n\n| Метод | Маршрут | Метод контроллера |\n|-------|------------------|-------------------|\n| POST | /articles | create |\n\nСоздание новой статьи начинается с отображения формы для ввода данных. Когда пользователь заполняет форму и отправляет ее, данные передаются на сервер с помощью POST-запроса.\n\nПример метода `create` в контроллере:\n\n```ruby\nclass ArticlesController < ApplicationController\n def create\n @article = Article.new(article_params)\n if @article.save\n redirect_to @article, notice: 'Статья успешно создана.'\n else\n render :new\n end\n end\n\n private\n\n def article_params\n params.require(:article).permit(:title, :body)\n end\n\nend\n```\n\nВ этом методе мы создаем новый объект `Article`, используя параметры, полученные из формы с помощью Strong Params. Strong Parameters — это механизм в Ruby on Rails, который помогает защитить приложение от уязвимостей, связанных с массовым присвоением (mass assignment). Он позволяет явно указывать, какие параметры могут быть использованы для создания или обновления объектов, что предотвращает возможность изменения нежелательных атрибутов.\n\nФорма для создания новой статьи отображается на странице, когда пользователь переходит по маршруту `GET /articles/new`. В контроллере `ArticlesController` метод `new` отвечает за инициализацию нового объекта `Article` и отображение формы.\n\n```ruby\nclass ArticlesController < ApplicationController\n # ...\n\n def new\n @article = Article.new\n end\n\n # ...\n\nend\n```\n\nПосле выполнения метода `new()`, Rails автоматически рендерит шаблон *app/views/articles/new.html.erb*, который содержит форму для создания новой статьи:\n\n```erb\n<h1>Создать новую статью</h1>\n\n<%= form_with model: @article, local: true do |form| %>\n <% if @article.errors.any? %>\n <div id=\"error_explanation\">\n <h2><%= pluralize(@article.errors.count, \"ошибка\") %>:</h2>\n <ul>\n <% @article.errors.full_messages.each do |message| %>\n <li><%= message %></li>\n <% end %>\n </ul>\n </div>\n <% end %>\n\n <div>\n <%= form.label :title %>\n <%= form.text_field :title %>\n </div>\n\n <div>\n <%= form.label :body %>\n <%= form.text_area :body %>\n </div>\n\n <div>\n <%= form.submit \"Создать\" %>\n </div>\n<% end %><h1>Создать новую статью</h1>\n<%= link_to 'Назад к списку', articles_path %>\n```\n\nЗдесь Используется хелпер `form_with`, который создает форму для объекта `@article`. Если при валидации возникли ошибки, они будут отображены в верхней части формы, что позволяет пользователю увидеть, что нужно исправить. Поля формы (`title` и `body`) автоматически связываются с атрибутами объекта `@article`.\n\nЕсли сохранение прошло успешно, происходит перенаправление на страницу статьи. В противном случае отображается форма с ошибками.\n\n### Чтение (Read)\n\n| Метод | Маршрут | Метод контроллера |\n|-------|----------------------|-------------------|\n| GET | /articles | index |\n| GET | /articles/{id} | show |\n\nЧтение статей включает в себя два действия: отображение списка всех статей и просмотр конкретной статьи.\n\nПример метода `index`:\n\n```ruby\nclass ArticlesController < ApplicationController\n # ...\n\n def index\n @articles = Article.all\n end\n\n # ...\nend\n```\n\nПеременная `@articles` будет доступна в представлении, что позволяет отобразить список статей.\n\n```erb\n<h1>Список статей</h1>\n\n<table>\n <thead>\n <tr>\n <th>Заголовок</th>\n <th>Действия</th>\n </tr>\n </thead>\n\n <tbody>\n <% @articles.each do |article| %>\n <tr>\n <td><%= link_to article.title, article %></td>\n <td>\n <%= link_to 'Редактировать', edit_article_path(article) %>\n </td>\n </tr>\n <% end %>\n </tbody>\n</table>\n\n<%= link_to 'Создать новую статью', new_article_path %>\n<%= link_to 'Назад к списку', articles_path %>\n```\n\nПример метода `show`:\n\n```ruby\nclass ArticlesController < ApplicationController\n # ...\n\n def show\n @article = Article.find(params[:id])\n end\n\n # ...\nend\n```\n\nМетод `index` получает все статьи из базы данных и передает их в представление. Метод `show` находит конкретную статью по ID и также передает ее в представление.\n\n### Обновление (Update)\n\n| Метод | Маршрут | Метод контроллера |\n|------------|----------------------|-------------------|\n| PATCH/PUT | /articles/{id} | update |\n\nОбновление статьи начинается с отображения формы редактирования, которая заполняется текущими данными статьи. После внесения изменений и отправки формы данные передаются на сервер с помощью PATCH или PUT-запроса.\n\nПример метода `update`:\n\n```ruby\nclass ArticlesController < ApplicationController\n # ...\n def update\n @article = Article.find(params[:id])\n if @article.update(article_params)\n redirect_to @article, notice: 'Статья успешно обновлена.'\n else\n render :edit\n end\n end\n\n # ...\nend\n```\n\nВ этом методе мы находим статью по ID и обновляем ее с новыми параметрами. Если обновление прошло успешно, происходит перенаправление на страницу статьи. В противном случае отображается форма редактирования с ошибками.\n\n```erb\n<h1>Редактировать статью</h1>\n\n<%= form_with model: @article, local: true do |form| %>\n <% if @article.errors.any? %>\n <div id=\"error_explanation\">\n <h2><%= pluralize(@article.errors.count, \"ошибка\") %>:</h2>\n <ul>\n <% @article.errors.full_messages.each do |message| %>\n <li><%= message %></li>\n <% end %>\n </ul>\n </div>\n <% end %>\n\n <div>\n <%= form.label :title %>\n <%= form.text_field :title %>\n </div>\n\n <div>\n <%= form.label :body %>\n <%= form.text_area :body %>\n </div>\n\n <div>\n <%= form.submit \"Обновить\" %>\n </div>\n<% end %>\n```\n\nЕсли при валидации возникли ошибки, они будут отображены в верхней части формы, что позволяет пользователю увидеть, что нужно исправить.\nПоля формы (`title` и `body`) автоматически заполняются текущими значениями статьи.\n\n### Удаление (Delete)\n\n| Метод | Маршрут | Метод контроллера |\n|--------|----------------------|-------------------|\n| DELETE | /articles/{id} | destroy |\n\nУдаление статьи происходит по запросу DELETE. Обычно это действие инициируется пользователем через кнопку \"Удалить\" на странице статьи или в списке статей.\n\nПример метода `destroy`:\n\n```ruby\nclass ArticlesController < ApplicationController\n # ...\n\n def destroy\n @article = Article.find(params[:id])\n @article.destroy\n redirect_to articles_path, notice: 'Статья успешно удалена.'\n end\n\n # ...\nend\n```\n\nВ этом методе мы находим статью по ID и вызываем метод `destroy`, чтобы удалить ее из базы данных. После успешного удаления происходит перенаправление на страницу со списком статей.\n\nЧтобы выполнить запрос на удаление ресурса из шаблона, мы должны использовать хелпер `link_to` с указанием метода `:delete`. Это позволяет отправить DELETE-запрос на сервер, когда пользователь нажимает на ссылку.\n\n```erb\n<%= link_to 'Удалить', article, data: { turbo_method: :delete, turbo_confirm: 'Вы уверены, что хотите удалить эту статью?' } %>\n```\n\n## Flash\n\nСообщения flash используются для отображения временных уведомлений пользователям, таких как сообщения об успехе или ошибках после отправки форм или выполнения других действий. Флеш-сообщения передаются в шаблон и там выводятся. После их извлечения хранилище обнуляется.\n\nФлеш-сообщения показываются только на один запрос. После обновления страницы или перехода в другое место они пропадают. Это удобно, так как не нужно следить за их жизненным циклом.\n\nВо многих веб-фреймворках, включая Ruby on Rails, типы флеш-сообщений часто стандартизированы, например:\n\n* **notice** или **success**: для информирования пользователя о том, что действие было выполнено успешно.\n\n* **error** для отображения ошибок, связанных с валидацией или другими проблемами. Например, сообщение о том, что введённые данные некорректны.\n\n* **info**: Используется для предоставления дополнительной информации пользователю, которая не является ни успешной, ни ошибочной. Например, сообщение о том, что действие будет выполнено позже.\n\n* **warning**: для предупреждения пользователя о потенциальных проблемах или рисках, связанных с его действиями.\n\n### Пример использования Flash\n\nВ контроллере Ruby on Rails мы можем использовать эти типы сообщений следующим образом:\n\n```ruby\ndef create\n @article = Article.new(article_params)\n if @article.save\n redirect_to @article, notice: 'Статья успешно создана.'\n else\n flash.now[:alert] = 'Ошибка при создании статьи. Пожалуйста, проверьте данные.'\n render :new\n end\nend\n```\n\nЧтобы вывести flash-сообщения в нашем приложении на Ruby on Rails, добавим соответствующий код в представление. Обычно это делается в основном макете приложения, чтобы сообщения отображались на всех страницах:\n\n*app/views/layouts/application.html.erb*:\n\n```erb\n<% if flash.any? %>\n <% flash.each do |key, value| %>\n <div class=\"<%= key %>\"><%= value %></div>\n <% end %>\n<% end %>\n```\n\n## Собираем все вместе\n\n### Итоговый контроллер: Полный разбор элементов CRUD\n\nСобираем все элементы CRUD в итоговом контроллере `ArticlesController`, который управляет статьями в нашем приложении. Контроллер включает следующие действия:\n\n- **create**: Создает новую статью, проверяет наличие ошибок и перенаправляет пользователя на страницу статьи или возвращает форму с сообщением об ошибках.\n- **new**: Инициализирует новый объект статьи для отображения формы создания.\n- **index**: Получает все статьи и отображает их в списке.\n- **show**: Находит и отображает конкретную статью по ее идентификатору.\n- **edit**: Находит статью для редактирования и отображает форму.\n- **update**: Обновляет существующую статью, проверяет наличие ошибок и перенаправляет пользователя или возвращает форму с сообщениями об ошибках.\n- **destroy**: Удаляет статью и перенаправляет пользователя на список статей или возвращает сообщение об ошибке, если удаление не удалось.\n\n```ruby\nclass ArticlesController < ApplicationController\n def create\n @article = Article.new(article_params)\n if @article.save\n flash[:notice] = 'Статья успешно создана'\n redirect_to @article\n else\n flash[:notice] = 'Упс, поправьте ошибки в форме'\n render :new, status: :unprocessable_entity\n end\n end\n\n def new\n @article = Article.new\n end\n\n def index\n @articles = Article.all\n end\n\n def show\n @article = Article.find(params[:id])\n end\n\n def edit\n @article = Article.find(params[:id])\n end\n\n def update\n @article = Article.find(params[:id])\n if @article.update(article_params)\n flash[:notice] = 'Статья успешно обновлена'\n redirect_to @article\n else\n flash[:notice] = 'Упс, поправьте ошибки в форме'\n render :edit, status: :unprocessable_entity\n end\n end\n\n def destroy\n @article = Article.find(params[:id])\n if @article.destroy\n flash[:notice] = 'Статья успешно удалена'\n redirect_to articles_path\n else\n flash[:notice] = 'Упс, не получилось удалить статью'\n redirect_back fallback_location: :articles_path\n end\n end\n\n private\n\n def article_params\n params.require(:article).permit(:title, :body)\n end\nend\n```\n\nКонтроллер также включает метод `article_params`, который использует Strong Parameters для обеспечения безопасности, разрешая только определенные параметры для массового присвоения.\n\n## Обработка HTTP-ошибок\n\nОбработка HTTP-ошибок позволяет обеспечить пользователям понятные и информативные сообщения об ошибках. В Ruby on Rails существует несколько способов обработки ошибок, которые позволяют разработчикам управлять различными ситуациями, такими как отсутствие запрашиваемых ресурсов, ошибки валидации и внутренние серверные ошибки.\n\nРассмотрим, как настроить маршруты для обработки ошибок, создать контроллер для управления этими ошибками и использовать механизмы Rails для перенаправления пользователей на соответствующие страницы ошибок.\n\n### Настройка обработки ошибок\n\n1. В Ruby on Rails настройка `config.exceptions_app = routes` в *config/application.rb* позволяет нам обрабатывать ошибки через маршруты приложения, что дает возможность создавать свои страницы ошибок.\n\n В файле `config/application.rb` добавим следующую строку:\n\n ```ruby\n config.exceptions_app = routes\n ```\n\n Это указывает фреймворку использовать маршруты приложения для обработки исключений.\n\n2. В файле `config/routes.rb` добавим маршруты для обработки ошибок:\n\n ```ruby\n Rails.application.routes.draw do\n get '/404', to: 'errors#not_found', as: :not_found_errors\n get '/500', to: 'errors#server_error', as: :server_error_errors\n\n # Обработка всех остальных маршрутов\n match '*unmatched', to: 'errors#not_found', via: :all\n end\n ```\n3. Создадим контроллер `ErrorsController` в соответствующем файле *app/controllers/errors_controller.rb*:\n\n ```ruby\n class ErrorsController < ApplicationController\n def not_found\n render status: :not_found\n end\n\n def server_error\n render status: :internal_server_error\n end\n end\n ```\n4. Создадим директорию `app/views/errors` и добавим в нее шаблоны для ошибок:\n\n *app/views/errors/not_found.html.erb*:\n\n ```erb\n <!-- app/views/errors/not_found.html.erb -->\n <h1>404 - Страница не найдена</h1>\n <p>Извините, но запрашиваемая страница не существует.</p>\n <%= link_to 'Вернуться на главную', root_path %>\n ```\n\n *app/views/errors/server_error.html.erb*:\n\n ```erb\n <h1>500 - Внутренняя ошибка сервера</h1>\n <p>Извините, произошла ошибка на сервере. Пожалуйста, попробуйте позже.</p>\n <%= link_to 'Вернуться на главную', root_path %>\n ```\n\nПоведение вывода ошибок зависит от конфигурации окружения, в котором работает приложение. Если `config.consider_all_requests_local` установлено в `true` (что является значением по умолчанию в development-среде), Rails будет показывать полные трассировки стека и подробные сообщения об ошибках в браузере. Это позволяет видеть, что пошло не так, и быстро исправлять ошибки.\n\nВ production-среде `consider_all_requests_local` обычно установлено в `false`. Rails не будет показывать полные трассировки стека. Вместо этого фреймворк будет отображать стандартные страницы ошибок (например, 404 или 500) или наши страницы ошибок.\n\nИзменить поведение вывода ошибок в разных окружениях, можно изменив *config/environments/*.rb* файлы. Например, в *config/environments/development.rb*:\n\n```ruby\nrequire 'active_support/core_ext/integer/time'\n\nRails.application.configure do\n # ...\n\n # Теперь будут выводиться страницы из представлений views/errors/*\n config.consider_all_requests_local = false\n\n # ...\nend\n```\n\nТеперь наше приложение настроено для обработки ошибок через маршруты, что позволяет предоставлять пользователям более информативные и стилизованные страницы ошибок. Это улучшает взаимодействие с приложением и делает его более удобным для пользователей. Мы можем дополнительно настроить стили и содержание этих страниц в зависимости от требований и дизайна приложения.\n\n## Заключение\n\nМы изучили создания CRUD-сущности в Ruby on Rails, мы рассмотрели ключевые этапы, включая настройку ресурсного роутинга, создание контроллеров и представлений, а также обработку HTTP-ошибок. Эти аспекты обеспечивают функциональность, удобство использования и надежность веб-приложения.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":7193,"name":"theory","url":"/courses/rails-basics/lessons/intro/theory_unit"}],"links":[{"id":424121,"name":"Rails Guides","url":"https://guides.rubyonrails.org/index.html\n"}],"ordered_units":[{"id":7193,"name":"theory","url":"/courses/rails-basics/lessons/intro/theory_unit"}],"id":3211,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с целями и задачами курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Этот курс посвящен Ruby on Rails – самому популярному фреймворку на Ruby. В этом курсе мы будем создавать простые веб-приложения и рассмотрим следующие темы:\n\n* Создание CRUD, валидация данных\n* ORM, создание сущностей и связи\n* Конфигурация приложения\n* Вложенные ресурсы\n* Тестирование и линтинг\n* i18n и локализация\n* Написание middlewares\n\nДля полноценного погружения рекомендуем выполнять все домашние задания и выкатывать результат на сервисы, подобные [Render](https://render.com).\n"},"id":234,"slug":"rails-basics","challenges_count":0,"name":"Ruby On Rails","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"В этом курсе вы изучите основы работы с фреймоворком Ruby on Rails. Вы узнаете о роутинге, контроллерах, шаблонизаторах, моделях и связях между ними. В итоге научитесь создавать полноценные сайты с CRUD действиями, писать тесты на них, работать с базой данных через ActiveRecord и миграции. Знания из этого курса помогут создать свой сайт и сделать его доступным в интернете.","kind":"basic","updated_at":"2026-01-20T11:46:14.961Z","language":"ruby","duration_cache":61080,"skills":["Создавать сайты на Rails, покрывать их автоматическими тестами и выкладывать на PaaS-сервисы","Основным концепциям фреймворка, таким как контроллеры, роутинг, модели, шаблоны и тесты.","Автоматизировать большинство рутинных задач с помощью генераторов, автоматических форм и интеграций фронтенда.","Использовать repl и эффективно отлаживать приложения","Основным принципам построения безопасных приложений."],"keywords":["ruby","rails","orm","rest api","job workers","архитертура","rake"],"lessons_count":16,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NzM3MiwicHVyIjoiYmxvYl9pZCJ9fQ==--3b3f159f182adb8eb21f75671f2839f8c4374db6/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":475,"slug":"rails","title":"Ruby on Rails","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"not_finished","order":null,"duration_in_months":4},"id":632,"slug":"rails","title":"Разработка на Ruby on Rails","subtitle":"Изучите Ruby, Rails и проектирование REST API","subtitle_for_lists":"Изучите Ruby, Rails и проектирование REST API","locale":"ru","current":true,"duration_in_months_text":"4 месяца","stack_slug":"rails","price_text":"от 3 900 ₽","duration_text":"4 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTExODcsInB1ciI6ImJsb2JfaWQifX0=--21b225d5b1f2f885f46e9ef4e79641fbea2f76dc/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Coding%20workshop-bro.webp"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/rails-basics/lessons/crud/theory_unit","version":"0b0c6d4ebbd40fd58630a0dd89cc25544ccdf24e","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Ruby On Rails</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: CRUD</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"CRUD","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Ruby On Rails"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Несмотря на огромное число разнообразных сайтов, практически всю веб-разработку можно свести к <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://ru.wikipedia.org/wiki/CRUD" rel="noopener noreferrer" target="_blank">CRUD</a>-операциям. В этом уроке мы познакомимся с ними подробнее.</p>
<h2 id="heading-2-1">Что такое CRUD</h2>
<p>CRUD — это широко распространенный термин, означающий четыре стандартные операции над любой сущностью. Рассмотрим такой пример для статей:</p>
<ul>
<li>Создание (<em>Create</em>) — добавление новой статьи.</li>
<li>Чтение (<em>Read</em>) — просмотр статьи пользователями сайта или в административном интерфейсе.</li>
<li>Обновление (<em>Update</em>) — редактирование содержимого статьи, изменение заголовка или категории.</li>
<li>Удаление (<em>Delete</em>) — удаление статьи из базы данных.</li>
</ul>
<p>Точно так же можно расписать действия над любыми другими ресурсами: комментариями к статьям, тегами, авторами и так далее. Чтобы создать полный CRUD для статей, нужно выполнить следующие действия:</p>
<ul>
<li>Создать сущность в коде (как правило, это класс).</li>
<li>Добавить таблицу в базу данных для хранения статей.</li>
<li>Написать тесты для проверки обработчиков статей.</li>
<li>Добавить обработчики для выполнения CRUD операций.</li>
<li>Добавить шаблоны для отображения статей на сайте.</li>
</ul>
<p>Ниже мы пройдемся по всему процессу создания CRUD статьи (сущность <em>Article</em>).</p>
<p>Начнем с <strong>роутинга</strong>. Полный CRUD для статей включает минимум семь маршрутов:</p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>Метод</th><th>Маршрут</th><th>Шаблон</th><th>Описание</th></tr></thead><tbody><tr><td>GET</td><td>/articles</td><td>articles/index.html.erb</td><td>Список статей</td></tr><tr><td>GET</td><td>/articles/{id}</td><td>articles/show.html.erb</td><td>Просмотр статьи</td></tr><tr><td>GET</td><td>/articles/new</td><td>articles/new.html.erb</td><td>Форма создания новой статьи</td></tr><tr><td>POST</td><td>/articles</td><td></td><td>Создание новой статьи</td></tr><tr><td>GET</td><td>/articles/{id}/edit</td><td>articles/edit.html.erb</td><td>Форма редактирования статьи</td></tr><tr><td>PATCH/PUT</td><td>/articles/{id}</td><td></td><td>Обновление статьи</td></tr><tr><td>DELETE</td><td>/articles/{id}</td><td></td><td>Удаление статьи</td></tr></tbody></table></div></div></div></div>
<p>Такое соглашение об именовании маршрутов изначально появилось в Ruby On Rails, а затем его адаптировали во многих других.</p>
<p>Ресурсный роутинг — это механизм в Ruby on Rails, который позволяет легко создавать маршруты для стандартных операций CRUD (Создание, Чтение, Обновление, Удаление) для ресурсов, таких как модели. Он значительно упрощает процесс определения маршрутов, так как автоматически создает все необходимые маршруты для работы с ресурсами.</p>
<p>Командой <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">bin/rails g resource</code> мы можем создать новый ресурс и полный набор файлов и кода, необходимых для работы с ресурсом. Давайте разберем, что именно делает эта команда и какие файлы она создает.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">bin/rails g resource article title:string body:text
invoke active_record
create db/migrate/20250116162806_create_articles.rb
create app/models/article.rb
invoke test_unit
create test/models/article_test.rb
create test/fixtures/articles.yml
invoke controller
create app/controllers/articles_controller.rb
invoke erb
create app/views/articles
invoke test_unit
create test/controllers/articles_controller_test.rb
invoke helper
create app/helpers/articles_helper.rb
invoke test_unit
invoke resource_route
route resources :articles</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">bin/rails</code>: Это исполняемый файл Rails, который запускает команды в вашем приложении.</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">g</code>: Сокращение от "generate", команда для генерации кода.</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">resource</code>: Указывает, что мы хотитим создать ресурс, который будет включать в себя маршруты, контроллер, представления и миграции.</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">article</code>: Имя ресурса, в данном случае это <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Article</code>.</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">title:string body:text</code>: Это атрибуты, которые будут добавлены к модели <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Article</code>. Здесь <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">title</code> будет строковым полем, а <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">body</code> — текстовым полем.</li>
</ul>
<p>При выполнении этой команды Rails создаст следующие файлы и изменения:</p>
<ol>
<li>
<p><strong>Миграция</strong>: Создается файл миграции в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">db/migrate</code>, который будет содержать код для создания таблицы <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">articles</code> с полями <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">title</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">body</code>.</p>
<p>Пример миграции:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class CreateArticles < ActiveRecord::Migration[6.0]
def change
create_table :articles do |t|
t.string :title
t.text :body
t.timestamps
end
end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
</li>
<li>
<p><strong>Модель</strong>: Создается файл модели <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">app/models/article.rb</code>, который будет представлять сущность <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Article</code>.</p>
<p>Пример модели:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class Article < ApplicationRecord
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
</li>
<li>
<p><strong>Контроллер</strong>: Создается контроллер <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">app/controllers/articles_controller.rb</code>, который будет обрабатывать запросы, связанные с ресурсом <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Article</code>.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class ArticlesController < ApplicationController
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
</li>
<li>
<p><strong>Представления</strong>: Создается директория <em>app/views/articles</em>, в которой будут находиться шаблоны представлений для действий контроллера (<em>index</em>, <em>show</em>, <em>new</em>, <em>edit</em>).</p>
</li>
<li>
<p><strong>Маршруты</strong>: В файл <em>config/routes.rb</em> будет добавлена строка для ресурсного роутинга:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">resources :articles</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
</li>
</ol>
<p>Команда генерации упрощает процесс создания нового ресурса в приложении Ruby on Rails, автоматически генерируя все необходимые компоненты для работы с сущностью. После выполнения этой команды останется только выполнить миграцию базы данных с помощью <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">bin/rails db:migrate</code> и начать использовать созданный ресурс.</p>
<p>Модель была создана пустой, мы можем добавить в нее валидацию присутствия полей:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class Article < ApplicationRecord
validates :title, presence: true
validates :body, presence: true
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>После генерации ресурса <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Article</code> с помощью команды <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">bin/rails g resource articles title:string body:text</code>, у нас есть все необходимые компоненты для работы с CRUD операциями. Давайте подробнее рассмотрим каждую из них.</p>
<h3 id="heading-3-2">Создание (Create)</h3>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>Метод</th><th>Маршрут</th><th>Метод контроллера</th></tr></thead><tbody><tr><td>POST</td><td>/articles</td><td>create</td></tr></tbody></table></div></div></div></div>
<p>Создание новой статьи начинается с отображения формы для ввода данных. Когда пользователь заполняет форму и отправляет ее, данные передаются на сервер с помощью POST-запроса.</p>
<p>Пример метода <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">create</code> в контроллере:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class ArticlesController < ApplicationController
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article, notice: 'Статья успешно создана.'
else
render :new
end
end
private
def article_params
params.require(:article).permit(:title, :body)
end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В этом методе мы создаем новый объект <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Article</code>, используя параметры, полученные из формы с помощью Strong Params. Strong Parameters — это механизм в Ruby on Rails, который помогает защитить приложение от уязвимостей, связанных с массовым присвоением (mass assignment). Он позволяет явно указывать, какие параметры могут быть использованы для создания или обновления объектов, что предотвращает возможность изменения нежелательных атрибутов.</p>
<p>Форма для создания новой статьи отображается на странице, когда пользователь переходит по маршруту <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">GET /articles/new</code>. В контроллере <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">ArticlesController</code> метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">new</code> отвечает за инициализацию нового объекта <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Article</code> и отображение формы.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class ArticlesController < ApplicationController
# ...
def new
@article = Article.new
end
# ...
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>После выполнения метода <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">new()</code>, Rails автоматически рендерит шаблон <em>app/views/articles/new.html.erb</em>, который содержит форму для создания новой статьи:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><h1>Создать новую статью</h1>
<%= form_with model: @article, local: true do |form| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@article.errors.count, "ошибка") %>:</h2>
<ul>
<% @article.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :body %>
<%= form.text_area :body %>
</div>
<div>
<%= form.submit "Создать" %>
</div>
<% end %><h1>Создать новую статью</h1>
<%= link_to 'Назад к списку', articles_path %></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Здесь Используется хелпер <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">form_with</code>, который создает форму для объекта <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">@article</code>. Если при валидации возникли ошибки, они будут отображены в верхней части формы, что позволяет пользователю увидеть, что нужно исправить. Поля формы (<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">title</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">body</code>) автоматически связываются с атрибутами объекта <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">@article</code>.</p>
<p>Если сохранение прошло успешно, происходит перенаправление на страницу статьи. В противном случае отображается форма с ошибками.</p>
<h3 id="heading-3-3">Чтение (Read)</h3>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>Метод</th><th>Маршрут</th><th>Метод контроллера</th></tr></thead><tbody><tr><td>GET</td><td>/articles</td><td>index</td></tr><tr><td>GET</td><td>/articles/{id}</td><td>show</td></tr></tbody></table></div></div></div></div>
<p>Чтение статей включает в себя два действия: отображение списка всех статей и просмотр конкретной статьи.</p>
<p>Пример метода <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">index</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class ArticlesController < ApplicationController
# ...
def index
@articles = Article.all
end
# ...
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Переменная <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">@articles</code> будет доступна в представлении, что позволяет отобразить список статей.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><h1>Список статей</h1>
<table>
<thead>
<tr>
<th>Заголовок</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<% @articles.each do |article| %>
<tr>
<td><%= link_to article.title, article %></td>
<td>
<%= link_to 'Редактировать', edit_article_path(article) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= link_to 'Создать новую статью', new_article_path %>
<%= link_to 'Назад к списку', articles_path %></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Пример метода <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">show</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class ArticlesController < ApplicationController
# ...
def show
@article = Article.find(params[:id])
end
# ...
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">index</code> получает все статьи из базы данных и передает их в представление. Метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">show</code> находит конкретную статью по ID и также передает ее в представление.</p>
<h3 id="heading-3-4">Обновление (Update)</h3>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>Метод</th><th>Маршрут</th><th>Метод контроллера</th></tr></thead><tbody><tr><td>PATCH/PUT</td><td>/articles/{id}</td><td>update</td></tr></tbody></table></div></div></div></div>
<p>Обновление статьи начинается с отображения формы редактирования, которая заполняется текущими данными статьи. После внесения изменений и отправки формы данные передаются на сервер с помощью PATCH или PUT-запроса.</p>
<p>Пример метода <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">update</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class ArticlesController < ApplicationController
# ...
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article, notice: 'Статья успешно обновлена.'
else
render :edit
end
end
# ...
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В этом методе мы находим статью по ID и обновляем ее с новыми параметрами. Если обновление прошло успешно, происходит перенаправление на страницу статьи. В противном случае отображается форма редактирования с ошибками.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><h1>Редактировать статью</h1>
<%= form_with model: @article, local: true do |form| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@article.errors.count, "ошибка") %>:</h2>
<ul>
<% @article.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :body %>
<%= form.text_area :body %>
</div>
<div>
<%= form.submit "Обновить" %>
</div>
<% end %></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если при валидации возникли ошибки, они будут отображены в верхней части формы, что позволяет пользователю увидеть, что нужно исправить.
Поля формы (<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">title</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">body</code>) автоматически заполняются текущими значениями статьи.</p>
<h3 id="heading-3-5">Удаление (Delete)</h3>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>Метод</th><th>Маршрут</th><th>Метод контроллера</th></tr></thead><tbody><tr><td>DELETE</td><td>/articles/{id}</td><td>destroy</td></tr></tbody></table></div></div></div></div>
<p>Удаление статьи происходит по запросу DELETE. Обычно это действие инициируется пользователем через кнопку "Удалить" на странице статьи или в списке статей.</p>
<p>Пример метода <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">destroy</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class ArticlesController < ApplicationController
# ...
def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to articles_path, notice: 'Статья успешно удалена.'
end
# ...
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В этом методе мы находим статью по ID и вызываем метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">destroy</code>, чтобы удалить ее из базы данных. После успешного удаления происходит перенаправление на страницу со списком статей.</p>
<p>Чтобы выполнить запрос на удаление ресурса из шаблона, мы должны использовать хелпер <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">link_to</code> с указанием метода <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">:delete</code>. Это позволяет отправить DELETE-запрос на сервер, когда пользователь нажимает на ссылку.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><%= link_to 'Удалить', article, data: { turbo_method: :delete, turbo_confirm: 'Вы уверены, что хотите удалить эту статью?' } %></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h2 id="heading-2-6">Flash</h2>
<p>Сообщения flash используются для отображения временных уведомлений пользователям, таких как сообщения об успехе или ошибках после отправки форм или выполнения других действий. Флеш-сообщения передаются в шаблон и там выводятся. После их извлечения хранилище обнуляется.</p>
<p>Флеш-сообщения показываются только на один запрос. После обновления страницы или перехода в другое место они пропадают. Это удобно, так как не нужно следить за их жизненным циклом.</p>
<p>Во многих веб-фреймворках, включая Ruby on Rails, типы флеш-сообщений часто стандартизированы, например:</p>
<ul>
<li>
<p><strong>notice</strong> или <strong>success</strong>: для информирования пользователя о том, что действие было выполнено успешно.</p>
</li>
<li>
<p><strong>error</strong> для отображения ошибок, связанных с валидацией или другими проблемами. Например, сообщение о том, что введённые данные некорректны.</p>
</li>
<li>
<p><strong>info</strong>: Используется для предоставления дополнительной информации пользователю, которая не является ни успешной, ни ошибочной. Например, сообщение о том, что действие будет выполнено позже.</p>
</li>
<li>
<p><strong>warning</strong>: для предупреждения пользователя о потенциальных проблемах или рисках, связанных с его действиями.</p>
</li>
</ul>
<h3 id="heading-3-7">Пример использования Flash</h3>
<p>В контроллере Ruby on Rails мы можем использовать эти типы сообщений следующим образом:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">def create
@article = Article.new(article_params)
if @article.save
redirect_to @article, notice: 'Статья успешно создана.'
else
flash.now[:alert] = 'Ошибка при создании статьи. Пожалуйста, проверьте данные.'
render :new
end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Чтобы вывести flash-сообщения в нашем приложении на Ruby on Rails, добавим соответствующий код в представление. Обычно это делается в основном макете приложения, чтобы сообщения отображались на всех страницах:</p>
<p><em>app/views/layouts/application.html.erb</em>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><% if flash.any? %>
<% flash.each do |key, value| %>
<div class="<%= key %>"><%= value %></div>
<% end %>
<% end %></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h2 id="heading-2-8">Собираем все вместе</h2>
<h3 id="heading-3-9">Итоговый контроллер: Полный разбор элементов CRUD</h3>
<p>Собираем все элементы CRUD в итоговом контроллере <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">ArticlesController</code>, который управляет статьями в нашем приложении. Контроллер включает следующие действия:</p>
<ul>
<li><strong>create</strong>: Создает новую статью, проверяет наличие ошибок и перенаправляет пользователя на страницу статьи или возвращает форму с сообщением об ошибках.</li>
<li><strong>new</strong>: Инициализирует новый объект статьи для отображения формы создания.</li>
<li><strong>index</strong>: Получает все статьи и отображает их в списке.</li>
<li><strong>show</strong>: Находит и отображает конкретную статью по ее идентификатору.</li>
<li><strong>edit</strong>: Находит статью для редактирования и отображает форму.</li>
<li><strong>update</strong>: Обновляет существующую статью, проверяет наличие ошибок и перенаправляет пользователя или возвращает форму с сообщениями об ошибках.</li>
<li><strong>destroy</strong>: Удаляет статью и перенаправляет пользователя на список статей или возвращает сообщение об ошибке, если удаление не удалось.</li>
</ul>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class ArticlesController < ApplicationController
def create
@article = Article.new(article_params)
if @article.save
flash[:notice] = 'Статья успешно создана'
redirect_to @article
else
flash[:notice] = 'Упс, поправьте ошибки в форме'
render :new, status: :unprocessable_entity
end
end
def new
@article = Article.new
end
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def edit
@article = Article.find(params[:id])
end
def update
@article = Article.find(params[:id])
if @article.update(article_params)
flash[:notice] = 'Статья успешно обновлена'
redirect_to @article
else
flash[:notice] = 'Упс, поправьте ошибки в форме'
render :edit, status: :unprocessable_entity
end
end
def destroy
@article = Article.find(params[:id])
if @article.destroy
flash[:notice] = 'Статья успешно удалена'
redirect_to articles_path
else
flash[:notice] = 'Упс, не получилось удалить статью'
redirect_back fallback_location: :articles_path
end
end
private
def article_params
params.require(:article).permit(:title, :body)
end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Контроллер также включает метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">article_params</code>, который использует Strong Parameters для обеспечения безопасности, разрешая только определенные параметры для массового присвоения.</p>
<h2 id="heading-2-10">Обработка HTTP-ошибок</h2>
<p>Обработка HTTP-ошибок позволяет обеспечить пользователям понятные и информативные сообщения об ошибках. В Ruby on Rails существует несколько способов обработки ошибок, которые позволяют разработчикам управлять различными ситуациями, такими как отсутствие запрашиваемых ресурсов, ошибки валидации и внутренние серверные ошибки.</p>
<p>Рассмотрим, как настроить маршруты для обработки ошибок, создать контроллер для управления этими ошибками и использовать механизмы Rails для перенаправления пользователей на соответствующие страницы ошибок.</p>
<h3 id="heading-3-11">Настройка обработки ошибок</h3>
<ol>
<li>
<p>В Ruby on Rails настройка <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">config.exceptions_app = routes</code> в <em>config/application.rb</em> позволяет нам обрабатывать ошибки через маршруты приложения, что дает возможность создавать свои страницы ошибок.</p>
<p>В файле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">config/application.rb</code> добавим следующую строку:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">config.exceptions_app = routes</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Это указывает фреймворку использовать маршруты приложения для обработки исключений.</p>
</li>
<li>
<p>В файле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">config/routes.rb</code> добавим маршруты для обработки ошибок:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">Rails.application.routes.draw do
get '/404', to: 'errors#not_found', as: :not_found_errors
get '/500', to: 'errors#server_error', as: :server_error_errors
# Обработка всех остальных маршрутов
match '*unmatched', to: 'errors#not_found', via: :all
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
</li>
<li>
<p>Создадим контроллер <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">ErrorsController</code> в соответствующем файле <em>app/controllers/errors_controller.rb</em>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">class ErrorsController < ApplicationController
def not_found
render status: :not_found
end
def server_error
render status: :internal_server_error
end
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
</li>
<li>
<p>Создадим директорию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">app/views/errors</code> и добавим в нее шаблоны для ошибок:</p>
<p><em>app/views/errors/not_found.html.erb</em>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><!-- app/views/errors/not_found.html.erb -->
<h1>404 - Страница не найдена</h1>
<p>Извините, но запрашиваемая страница не существует.</p>
<%= link_to 'Вернуться на главную', root_path %></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p><em>app/views/errors/server_error.html.erb</em>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><h1>500 - Внутренняя ошибка сервера</h1>
<p>Извините, произошла ошибка на сервере. Пожалуйста, попробуйте позже.</p>
<%= link_to 'Вернуться на главную', root_path %></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
</li>
</ol>
<p>Поведение вывода ошибок зависит от конфигурации окружения, в котором работает приложение. Если <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">config.consider_all_requests_local</code> установлено в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">true</code> (что является значением по умолчанию в development-среде), Rails будет показывать полные трассировки стека и подробные сообщения об ошибках в браузере. Это позволяет видеть, что пошло не так, и быстро исправлять ошибки.</p>
<p>В production-среде <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">consider_all_requests_local</code> обычно установлено в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">false</code>. Rails не будет показывать полные трассировки стека. Вместо этого фреймворк будет отображать стандартные страницы ошибок (например, 404 или 500) или наши страницы ошибок.</p>
<p>Изменить поведение вывода ошибок в разных окружениях, можно изменив <em>config/environments/</em>.rb* файлы. Например, в <em>config/environments/development.rb</em>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">require 'active_support/core_ext/integer/time'
Rails.application.configure do
# ...
# Теперь будут выводиться страницы из представлений views/errors/*
config.consider_all_requests_local = false
# ...
end</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Теперь наше приложение настроено для обработки ошибок через маршруты, что позволяет предоставлять пользователям более информативные и стилизованные страницы ошибок. Это улучшает взаимодействие с приложением и делает его более удобным для пользователей. Мы можем дополнительно настроить стили и содержание этих страниц в зависимости от требований и дизайна приложения.</p>
<h2 id="heading-2-12">Заключение</h2>
<p>Мы изучили создания CRUD-сущности в Ruby on Rails, мы рассмотрели ключевые этапы, включая настройку ресурсного роутинга, создание контроллеров и представлений, а также обработку HTTP-ошибок. Эти аспекты обеспечивают функциональность, удобство использования и надежность веб-приложения.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/rails?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">4 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Разработка на Ruby on Rails</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Ruby, Rails и проектирование REST API</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTExODcsInB1ciI6ImJsb2JfaWQifX0=--21b225d5b1f2f885f46e9ef4e79641fbea2f76dc/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Coding%20workshop-bro.webp" alt="Разработка на Ruby on Rails" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/rails-basics/lessons/crud/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 16</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/rails-basics/lessons/crud/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-CdBlNCiQ.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-nkZBEvfU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>