#ruby-on-rails #activerecord #rails-activerecord
#ruby-on-rails #activerecord #rails-activerecord
Вопрос:
У меня есть модель «Пользователь», которая имеет отношение «один ко многим» с другой моделью «Членство». Членство имеет поле с именем «группа», в котором указано имя группы. Я хочу запросить все записи пользователей, которые находятся в определенной «группе», и хочу загрузить все «членства» для каждого возвращенного пользователя.
Прежде чем я пойду дальше, я знаю, что обычно вы реализуете «групповую» модель и превращаете членство в таблицу соединений между пользователем и группой и просто запрашиваете конкретную группу для всех ее пользователей. Этот пример немного надуманный, но он представляет реальный случай, над которым я работаю.
Во всяком случае, я попробовал выполнить следующий запрос:
User.includes(:memberships).where(memberships: { group: groupname })
Однако, хотя это возвращает правильных пользователей, оно загружает только те членства, которые соответствуют запросу. Другими словами, если пользователь «Bob» находится в «красной» и «синей» группах, и я запрашиваю всех пользователей «синей» группы, пользователь «Bob» возвращается среди результатов, но загружается только его членство в «синей» группе.
Есть ли способ сделать это с помощью одного запроса к БД И нетерпеливо загружать все членства каждого возвращенного пользователя?
Ответ №1:
В вашем примере выполняется один запрос к БД, поскольку для ассоциации существует условие запроса. Обычно includes
выполняется два запроса к БД: один для загрузки пользователей, а другой для загрузки связанных членств.
Я обратил на это внимание, потому что есть решение, которое работает при выполнении двух запросов к БД, и оно работает нормально (поведение при загрузке ActiveRecord по умолчанию)
User.preload(:memberships).joins(:memberships).where(memberships: {group: groupname})
Примечание: в примере preload
метод используется вместо includes
. Это означает, что условия запроса в связанной таблице должны применяться только в первом запросе и не влиять на второй запрос, который предварительно memberships
загружается . По умолчанию, когда в связанной таблице нет дополнительных условий запроса include
, работает как preload
.
Первый запрос получит всех пользователей, имеющих членство в требуемой группе.
Второй запрос извлечет все связанные членства без каких-либо дополнительных условий.
Комментарии:
1. Это выглядит хорошо. Да, я сказал «единый запрос к БД», но на самом деле я имел в виду «фиксированный набор запросов независимо от количества пользователей», так что это работает хорошо. Просто чтобы убедиться, что я понимаю, что здесь происходит, «joins» выполняет внутреннее объединение, поэтому предложение «where» может выполнять сопоставление для пользователей на основе имени группы, но фактически не загружает никаких данных о членстве. Вызов «preload» фактически загружает данные членства в память, но, поскольку он выполняет это в отдельном запросе, на него не влияет предложение «where». Это звучит правильно?
2. Это верно. Я также хотел бы уточнить одну вещь: на вызов «preload» не влияет предложение «where» не только потому, что это отдельный запрос, но и потому, что Rails не добавляет предложение «where» к запросу. Если бы вы использовали
include
вместоpreload
, у вас было бы одно и то же предложение «where» в обоих запросах. На самом деле, я не могу сказать, что это ожидаемое поведение, просто так оно работает.3. Спасибо. Я всегда задавался вопросом, зачем кому-то вообще использовать предварительную загрузку вместо включения. Теперь я знаю.