Rinat Shaykhutdinov

Ruby developer, DJ, musician from St. Petersburg, Russia

Делаем HABTM в ActiveRecord ещё вкуснее

В интернете не очень много русскоязычной информации по Руби и Рельсам, а если дело касается вещей сложнее чем «rails g model Post», то спасает только stackoverflow. Многие новички, особенно перебежники из php-лагеря, начиная своё знакомство с Рельсами очень радуются такой вещи как ActiveRecord, которая берёт на себя всю головную боль по формированию запросов в базу. Вот только в реальной жизни на highload-проектах ORM играет довольно злые шутки. Под катом подробности.

Предыстория

В проекте над которым я сейчас работаю, как и во многих других проектах есть сущность «Пользователь». У меня стояла задача внедрить возможность отправлять пользователям уведомления, которые бы висели на странице до момента их принудительного закрытия пользователем. Схема работы такова: будем считать что табличка с пользователями (users) у нас уже есть, ровно как и табличка с оповещениями (notifications). Создаём join-таблицу notifications_users.

Для связи моделей используем has_and_belongs_to_many.

Описание проблемы

Нам удобно в админке при создании оповещения указывать какой группе пользователей его отправить, соответственно для нас cамый простой способ назначить оповещение нескольким пользователям — collection_singular_ids=ids.

Запускаем тесты и видим, что в результате мы получаем в лог кучу однотипных запросов с инзертами в join-таблицу, которые неплохо бы группировать в один запрос, т. к. при большом количестве пользователей каждая подобная рассылка уведомлений может оказаться последней для нашей репликации и 2-х прилежащих к ней ночей. В проекте используется MySQL, поэтому моё решение возможно вам не подойдёт.

Подключаем волшебный gem activerecord-import.

gem "activerecord-import", ">= 0.2.0"

Переписываем метод users_ids=(ids) для нашей модели уведомлений. Хвала перегрузке методов!

В чём соль

В результате, гора запросов на вставку в таблицу notifications_users превратилась в один лаконичный, аккуратный и быстрый insert. Поблагодарим за это gem activerecord-import, который добавил к нашей модели несколько дополнительных методов (в т. ч. и import), позволяющих производить более сложные запросы, учитывающие специфику MySQL. Если вам понадобится сделать «INSERT IGNORE» или «ON DUPLICATE KEY UPDATE», то обратите своё внимание именно на этот gem. В следующих постах я ещё не раз затрону вопросы оптимизации приложения под большие нагрузки и более подробно опишу работу с этим gem’ом.

8 октября 2011 г.