В данной статье, я покажу почему нужно использовать 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 окружении и показал себя очень хорошо. Несмотря на его простоту реализации, он может сильно улучшить производительность вашего проекта, т.к. чем больше вы используете его, тем больше сохраняете, ознакомьтесь с групповыми бенчмарками для деталей.


Любые отзывы приветствуются! Признательны за любой вклад в проект!

Комментарии (2)


  1. Bloxy
    02.12.2018 22:27

    Чем использование этого gem отличается от
    config.active_record.belongs_to_required_by_default = false
    ?


    1. djezzzl Автор
      02.12.2018 13:38

      В вашем случае, вы просто отключаете валидацию по умолчанию. Да, в итоге вы не будете делать SELECT запрос к базе данных на каждую из ваших связей. Но задача целостности базы данных все еще не решена, и в вашем случае, в базу данных можно будет сохранить все, что угодно. В случае же с гемом, вы получаете целостность, улучшенную производительность и совместимость с ActiveModel::Errors