В Rails — как сделать один запрос с несколькими запросами?

Здесь есть 3 модели:

  • проекты
  • темы (project_id)
  • thread_participations (thread_id, логическое значение)

Прямо сейчас у меня есть список проектов пользователя, и список показывает, сколько потоков непрочитано для каждого проекта. Огромная проблема здесь заключается в том, что если у пользователя есть несколько проектов (что делают все пользователи), это приводит к тому, что БД получает несколько запросов, по одному на проект.

Я хотел бы использовать Rails для создания запроса, который при одном попадании в БД возвращает количество непрочитанных сообщений для каждого проекта пользователя.

Вот что я использую сегодня в представлении:

<% @projects.each_with_index do |project, i| %>
<%=project %>: <%= Thread.unread(current_user,project).count %>
<% end %>

И в теме Модель:

  scope :unread, lambda { |user,project| 
      includes(:project,:thread_participations).where(:project_id => project.id, :thread_participations => {:read => false, :user_id => user.id})
  }

Любые предложения о том, как это сделать? И в какой модели это должно жить? Может быть, модель пользователя, поскольку она не зависит от проекта или потока?

Спасибо


person AnApprentice    schedule 05.02.2011    source источник


Ответы (1)


Есть несколько способов структурировать этот запрос, но вот один из них.

Вы можете выполнить это в одном запросе, а затем перебрать результаты. Я бы сначала создал область участия в потоках для непрочитанных для определенного пользователя. Затем используйте область действия и включите все потоки и проекты, сгруппируйте по идентификатору проекта (чтобы вы получали непрочитанные потоки для этого проекта), а затем подсчитайте количество непрочитанных потоков, подсчитав threads.id:

class ThreadParticipations
  scope :unread, lambda{ |user| where(user_id: user.id, read: false) }
end

ThreadParticipations
  .unread(current_user)
  .includes(:thread => :project)
  .group('projects.id')
  .count('threads.id')

=> { 10 => 15, 11 => 10 }
# project(10) has 15 unread threads and project(11) has 10 unread threads
person Pan Thomakos    schedule 05.02.2011
comment
Спасибо, я не уверен, что следую 100%, поэтому позвольте мне спросить... Почему область непрочитанных сообщений в ThreadParticipations, а не в Thread, как сейчас? Просто любопытно. Кроме того, 2-й блок (ThreadParticipations .unread(current_user...) где он живет? контроллер? спасибо - person AnApprentice; 06.02.2011
comment
Еще одна вещь, непрочитанная область не фильтруется по project_id? - person AnApprentice; 06.02.2011
comment
Причина, по которой вы не фильтруете по project_id, заключается в том, что вы хотите включить в свой запрос все проекты пользователя. Если вы отфильтруете по project_id, вы вернетесь к исходной проблеме выполнения нескольких запросов (по одному для каждого проекта). - person Pan Thomakos; 06.02.2011
comment
Второй блок может находиться в любом месте, где вы хотите сделать вызов - он просто есть в моем коде в качестве примера. - person Pan Thomakos; 06.02.2011
comment
Я переместил его в участие в потоках, потому что это то, что запрашивает область. Он запрашивает пользователя и непрочитанный - как об участии в потоке, а не о потоке. - person Pan Thomakos; 06.02.2011
comment
@pan это чертовски хороший момент :) Я собираюсь попробовать это сейчас. сообщим в ближайшее время. Спасибо! - person AnApprentice; 06.02.2011
comment
Обновите, наткнулись на несколько дорожных блоков. Во-первых, с областью действия ошибок, я считаю, что должно быть where(:user_id => user.id, :read => false), а затем в блоке .group('project.id'), что ошибки с PGError: ОШИБКА: синтаксическая ошибка в группе LINE 1 или рядом с ней: ...thread_participations.read = 'f') GROUP BY project.id - person AnApprentice; 06.02.2011
comment
Эй, я обновил свой пост с предложением user.id. Кроме того, вы должны группировать по проектам.id, а не по project.id - это было моим недостатком. - person Pan Thomakos; 06.02.2011
comment
@Pan, пробую сейчас... Но в лямбде у вас есть user_id: user.id, разве он не должен быть с =› - person AnApprentice; 06.02.2011
comment
@Pan, все еще получаю PGError: ERROR: синтаксическая ошибка в группе или рядом с ней - person AnApprentice; 06.02.2011
comment
Думаю, я заставил его работать, просто выполнив .count вместо .count('threads.id'). Но что делать с этими результатами? Я получаю, 1, 1] [2, 3], идея состоит в том, чтобы показать список всех групп и их количество непрочитанных, поэтому я не уверен, что это за результаты? Кроме того, есть ли способ получить 0 для группы с 0 записями, а не ничего? Может быть, пример, показывающий, как использовать этот запрос? - person AnApprentice; 06.02.2011
comment
Я предполагаю, что вы используете PostgreSQL - я бы посоветовал проверить сгенерированный синтаксис SQL и убедиться, что он выглядит правильно. Возможно, вам потребуется изменить имена некоторых атрибутов или что-то в этом роде. У меня нет полного доступа к вашим моделям, поэтому я не могу вам помочь. Это решение работает в MySQL. - person Pan Thomakos; 06.02.2011
comment
В моем решении => { 10 => 15, 11 => 10 } указывает на образец вывода. Обычно это означает project_id и количество непрочитанных сообщений. В вашем выводе project_id, вероятно, равен 1, а затем 2, а количество непрочитанных сообщений - 1 и 3 соответственно. - person Pan Thomakos; 06.02.2011
comment
Я не знаю, что вы имеете в виду, получая 0 для групп с 0 записями. - person Pan Thomakos; 06.02.2011
comment
@pan, поэтому цель состоит в том, чтобы вывести список всех проектов пользователя с количеством непрочитанных проектов пользователя рядом с каждым проектом. Я пытаюсь узнать, как этот запрос превращается в этот вывод. И запрос, похоже, игнорирует проект, если счетчик равен 0. Имеет ли это смысл? Кстати, спасибо за вашу помощь в этом! - person AnApprentice; 06.02.2011
comment
Он игнорирует проекты, у которых нет потоков. Вы можете обойти это, используя левое соединение, например так: .joins('LEFT JOIN threads on threads.id = thread_participations.thread_id LEFT JOIN проектов on Projects.id = threads.project_id') вместо оператора включения. - person Pan Thomakos; 06.02.2011
comment
Спасибо пробовал, пока работало, вывод был тот же. IDK, может быть, есть какие-то другие идеи, чтобы заставить это работать. - person AnApprentice; 06.02.2011
comment
Допустим, это сработало, как добиться цели вывода списка каждой группы независимо от того, а затем количество этой группы для пользователя? Учитывая, что запрос возвращает массив? - person AnApprentice; 06.02.2011
comment
Может быть интересно запросить все проекты пользователя и включить поле, которое представляет собой COUNT непрочитанных пользователем. по сравнению с запуском в ThreadParticipations. С SQL это было довольно просто с подзапросом или запросом в запросе, но с рельсами я еще не понял, как это волшебство работает. - person AnApprentice; 06.02.2011
comment
Вам придется провести собственное расследование, чтобы продвинуться дальше в вашем конкретном случае. Существует множество вариантов этого запроса, но основная концепция одна и та же: включите нужные таблицы, сгруппируйте по атрибуту, с которым хотите связать, а затем подсчитайте. Мое решение должно предоставить вам основы выполнения запроса с одним счетчиком, а не по одному для каждого проекта. Насколько я понимаю, вы не можете получить проекты, с которыми связан пользователь, у которых нет потоков, потому что в вашем исходном сообщении нет ничего, указывающего, как вы могли бы связать пользователя с проектом без потоков. - person Pan Thomakos; 06.02.2011
comment
Спасибо Пан. Только один последний вопрос здесь. так что, учитывая, что приведенный выше запрос отправляет обратно [1, 1] [2, 3], [5,1], где [X, Y] x — это идентификатор проекта, а y — количество непрочитанных сообщений. Как вы что-то делаете с этими данными? Вы знаете, как найти, чтобы я мог сказать, найти group_id 7 в X? Если не найдено показать 0? Действительно изо всех сил пытается соединить точки здесь. Был бы очень признателен за знания о том, как использовать набор результатов запроса. Спасибо! Или хотя бы точку в правильном направлении. еще раз спасибо! - person AnApprentice; 06.02.2011
comment
Если вам нужны проекты, которые этот пользователь еще не читал, вы можете использовать свой массив, чтобы получить все проекты в одном запросе: Project.find([[1,1],[2,3],[5,1] ].map{ |k|k[0] }). Вы также можете использовать список пользовательских проектов для заполнения счетчика непрочитанных сообщений, обратившись к этому массиву и заполнив переменную unread_count в каждом проекте для соответствующего идентификатора проекта. Как вы используете массив, зависит от вас, но если вы хотите отобразить дополнительную информацию о проекте, помимо id и unread_count, вам придется выполнить некоторую дополнительную обработку массива, чтобы привести его в нужный формат. - person Pan Thomakos; 06.02.2011
comment
@Пан, спасибо. Я думаю, что было бы лучше вернуть массив как объект JSON и позволить jQuery позаботиться об этом. - person AnApprentice; 06.02.2011