Трюки з запитами ActiveRecord

2 хв. читання

Моя найулюбленіша частина 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])`
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 6.2K
Приєднався: 7 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація