Моя найулюбленіша частина Rails – це скоупи (scopes) ActiveRecord через можливість їх багаторазового використання.
Нижче приведено декілька дієвих трюків з ActiveRecord, які можуть допомогти при розробці.
1. Join-запит з умовою до асоціативних таблиць
Припустимо, що ви маєте таблицю users
з асоціацією profile
. Якщо вам необхідно зробити запит, щоб отримати користувачів профілі яких затверджено, ви можете зробити наступне:
# User model
scope :activated, ->{
joins(:profile).where(profiles: { activated: true })
}
Цей підхід працює, проте не є коректним: логіка profile
тепер знаходиться у моделі user
. Така реалізація порушує правило інкапсуляції об'єктно-орієнтованого підходу .
Можна запропонувати наступний варіант:
# Profile model
scope :activated, ->{ where(activated: true) }
# User model
scope :activated, ->{ joins(:profile).merge(Profile.activated) }
Такий спосіб тримає запити окремо від логіки.
2. Різні типи вкладених joins
Потрібно приділяти увагу використанню joins
в ActiveRecord. Наприклад, user
має один profile
, а profile
має багато skills
. За замовчуванням використовується INNER JOIN
.
Приклад:
User.joins(:profiles).merge(Profile.joins(:skills))
=> SELECT users.* FROM users
INNER JOIN profiles ON profiles.user_id = users.id
LEFT OUTER JOIN skills ON skills.profile_id = profiles.id
Проте такий варіант правильніший:
User.joins(profiles: :skills)
=> SELECT users.* FROM users
INNER JOIN profiles ON profiles.user_id = users.id
INNER JOIN skills ON skills.profile_id = profiles.id
3. Exist-запит
Якщо необхідно отримати список користувачів без відомих постів, ви використовуєте NOT EXISTS
. Ви можете зробити це дещо зручніше з методами, приведеними нижче:
# Post
scope :famous, ->{ where("view_count > ?", 1_000) }
# User
scope :without_famous_post, ->{
where(_not_exists(Post.where("posts.user_id = users.id").famous))
}
def self._not_exists(scope)
"NOT #{_exists(scope)}"
end
def self._exists(scope)
"EXISTS(#{scope.to_sql})"
end
Можна дотримуватися схожого шаблону і для EXISTS
.
4. Підзапити
Скажімо, що вам необхідно отримати повідомлення, зроблені підмножиною користувачів, то скоріше за все ви використаєте:
Post.where(user_id: User.created_last_month.pluck(:id))
Проблема в тому, що два запити SQL будуть виконуватися одночасно: Перший – для отримання ідентифікаторів користувачів. Другий – для отримання повідомлень від цих користувачів.
Проте, можна досягнути такого ж результату за допомогою одного запиту, що містить підзапит:
Post.where(user_id: User.created_last_month)
ActiveRecord обробляє його за вас.
5. Повернення до основ
Не забувайте, що запити ActiveRecord можна додати до .to_sql
для генерації рядка SQL і через .explain
, щоб отримати деталі, оцінки складності, тощо.
6. Булеві функції
Можливо, щоб згенерувати SELECT users.* FROM users WHERE users.tall <> 't'
, ви використовуєте:
User.where.not(tall: true)
Такий запит поверне користувачів, де tall
має значення false
, але не тих, що мають tall
, який містить null
.
Ви маєте написати:
User.where("users.tall IS NOT TRUE")` або `User.where(tall: [false, nil])`
Ще немає коментарів