ActiveRecord в Ruby on Rails — это мощный ORM, но даже опытные разработчики часто допускают ошибки, которые убивают производительность. В этой статье разберём 10 типичных проблем с запросами к PostgreSQL и покажем, как их исправлять — от N+1 до неожиданного поведения scope.
ActiveRecord — мощный инструмент. Но с ним легко сделать красиво… и при этом ужасно неэффективно. Вот 10 ошибок, которые совершают даже опытные разработчики.
1. 🔁 N+1 при each
User.all.each { |u| puts u.company.name }
# 💥 Генерирует 1 + N запросов
🛠 Решение: includes(:company)
2. 💥 pluck(:id) в подзапросах
Post.where(user_id: User.active.pluck(:id))
Загрузит все id в память. Может быть очень дорого.
🛠 Лучше: User.active.select(:id) или .ids
3. 🧊 Потеря полей при select(...)
User.select("id").first.name # => nil
🛠 Выбирай все нужные поля или используй pluck
4. 🤷♂️ Забыл .save или .update
user.name = "John"
# и ничего не происходит 😅
🛠 Используй update или не забывай про save
5. 🧠 Делаешь запросы в цикле
users.each { |u| u.posts.count }
🛠 Или includes(:posts) + u.posts.size
6. ⚠️ Используешь default_scope
Он прилипает ко всем запросам и иногда ведёт себя очень неочевидно.
🛠 Предпочти явные scope :active, -> { where(active: true) }
7. 🧱 Забыл .readonly в сложных joins
Когда строишь нестандартный SQL, Rails может попытаться сохранить объект. Это приведёт к ошибке или багу.
🛠 Добавь .readonly
8. 📉 Используешь .count на коллекции
users = User.all
users.count # => SELECT COUNT(*) FROM users
users.to_a.count # => Ruby-level count
🛠 Делай .count до загрузки
9. 🌀 Путаешь joins и includes
User.joins(:posts).where(posts: { title: "foo" }) # ok
User.includes(:posts).where(posts: { title: "foo" }) # => иногда делает JOIN, иногда нет
🛠 Если фильтруешь — используй joins
10. 🚀 Не замеряешь запросы
Rails — не угадает за тебя. Используй bullet, rack-mini-profiler и EXPLAIN ANALYZE
📌 Резюме
| Ошибка | Что делать вместо |
|---|---|
| N+1 | includes или preload |
pluck(:id) |
select(:id) или .ids |
select(...) |
Добавляй нужные поля явно |
| Циклы запросов | Группировка или предварительная загрузка |
default_scope |
Используй именованные scope‘ы |