В данной статье, я покажу почему нужно использовать db_belongs_to
из database_validations гема вместо привычного нам belongs_to
.
Я уверен, что большинство из вас знакомо с belongs_to
из ORM ActiveRecord. Но знаете ли вы, что инициализация связи с помощью belongs_to
в вашей модели также добавляет валидацию на существование связи. Это происходит потому, что belongs_to
имеет опцию optional: false
по умолчанию.
Таким образом, каждый раз когда вы сохраняете новый объект или обновляете существующий, вы выполняете дополнительный SQL SELECT запрос на каждую из ваших связей.
Пример
class User < ActiveRecord::Base
belongs_to :company
belongs_to :country
end
user = User.first
user.update(some_field: 'something')
# В первую очередь сделает два дополнительных запроса SELECT, чтобы узнать, что связи `company` и `country` существуют
Вам также следует знать, что данный подход не гарантирует целостность базы данных, т.к. связи могут быть в дальнейшем удалены (в последующих или параллельных запросах) без каких-либо проблем.
user.update(...)
user.company.destroy!
=> команда будет выполнена без ошибок, таким образом, в базе данных будет храниться пользователь без связи с компанией, что нарушает целостность нашей базы данных (если изначально не было задумано обратное)
Чтобы решить данную проблему, вы можете добавить соответствующее ограничение внешнего ключа (foreign key constraint) в вашу базу данных. Имея данное ограничение, вы всегда будете уверены, что данная связь существует.
Что насчет производительности? Зачем нам делать SELECT запросы к базе данных, если мы уже уверены, что целостность данных будет соблюдена (с использованием ограничения внешнего ключа)?
Ответ прост — не нужно. Но, чтобы это стало возможным, нам нужно решить проблему обработки исключения ActiveRecord::InvalidForeignKey
, которое мы получаем, когда пытаемся сохранить отсутствующую в базе данных связь. Это необходимо, чтобы иметь такое же поведение, как с belongs_to
, чтобы errors
включал такие же ошибки.
Чтобы не писать всю обработку самостоятельно, вам пригодится database_validations гем. Данный гем уже включал в себя validates_db_uniqueness_of
, который понравился людям (пост на хабре) и теперь имеет db_belongs_to
, который очень легко внедрить в ваш проект. db_belongs_to
улучшает производительность и гарантирует целостность данных.
Метод делает несколько вещей за вас:
- Проверяет существование правильного ограничения внешнего ключа в базе данных во время запуска приложения;
- Парсит исключения базы данных и предоставляет соответствующие ошибки для
errors
вашего объекта; - Исключает необходимость исполнения лишних SQL запросов к базе данных;
Я рекомендую вам использовать database_validations в ваших проектах. Данный гем уже протестирован в production окружении и показал себя очень хорошо. Несмотря на его простоту реализации, он может сильно улучшить производительность вашего проекта, т.к. чем больше вы используете его, тем больше сохраняете, ознакомьтесь с групповыми бенчмарками для деталей.
Любые отзывы приветствуются! Признательны за любой вклад в проект!
Bloxy
Чем использование этого gem отличается от
config.active_record.belongs_to_required_by_default = false
?
djezzzl Автор
В вашем случае, вы просто отключаете валидацию по умолчанию. Да, в итоге вы не будете делать SELECT запрос к базе данных на каждую из ваших связей. Но задача целостности базы данных все еще не решена, и в вашем случае, в базу данных можно будет сохранить все, что угодно. В случае же с гемом, вы получаете целостность, улучшенную производительность и совместимость с
ActiveModel::Errors