Как отфильтровать association_ids для модели ActiveRecord? - PullRequest
3 голосов
/ 22 декабря 2011

В домене, подобном этому:

 class User
  has_many :posts
  has_many :topics, :through => :posts
 end
 class Post
   belongs_to :user
   belongs_to :topic
 end
 class Topic
   has_many :posts
 end

Я могу прочитать все идентификаторы тем через user.topic_ids, но я не вижу способа применить условия фильтрации к этому методу, так как он возвращает Array вместо ActiveRecord::Relation.

Проблема заключается в том, что для пользователя задан существующий набор тем, отмечающий те, для которых есть сообщение пользователя.В настоящее время я делаю что-то вроде этого:

 def mark_topics_with_post(user, topics)
   # only returns the ids of the topics for which this user has a post
   topic_ids = user.topic_ids 
   topics.each {|t| t[:has_post]=topic_ids.include(t.id)}
 end 

Но это загружает все идентификаторы темы независимо от набора ввода.В идеале я хотел бы сделать что-то вроде

 def mark_topics_with_post(user, topics)
   # only returns the topics where user has a post within the subset of interest
   topic_ids = user.topic_ids.where(:id=>topics.map(&:id))
   topics.each {|t| t[:has_post]=topic_ids.include(t.id)}
 end 

Но единственное, что я могу сделать конкретно, это

 def mark_topics_with_post(user, topics)
   # needlessly create Post objects only to unwrap them later
   topic_ids = user.posts.where(:topic_id=>topics.map(&:id)).select(:topic_id).map(&:topic_id)
   topics.each {|t| t[:has_post]=topic_ids.include(t.id)}
 end 

Есть ли лучший способ?Можно ли иметь что-то вроде select_values в ассоциации или области?FWIW, я на рельсах 3.0.x, но мне было бы любопытно и 3,1.

Зачем я это делаю?

По сути, у меня естьСтраница результатов для полусложного поиска (который происходит только на основе данных Темы), и я хочу отметить результаты (Темы) как материал, с которым взаимодействовал пользователь (написал сообщение).

Так что, да, есть еще один вариант, который будет выполнять объединение [Тема, сообщение], чтобы результаты появлялись как отмеченные или нет в результате поиска, но это разрушило бы мою способность кешировать запрос Темы (запрос, даже без объединения, обходится дороже, чем выборка только идентификаторов для пользователя)

Обратите внимание на подходы, описанные выше действительно работают , они просто кажутся неоптимальными.

Ответы [ 2 ]

3 голосов
/ 22 декабря 2011

Я думаю, что ваше второе решение является почти оптимальным (с точки зрения рассматриваемых запросов), по крайней мере, относительно того, которое вы хотели бы использовать.

user.topic_ids генерируетзапрос:

SELECT `topics`.id FROM `topics` 
INNER JOIN `posts` ON `topics`.`id` = `posts`.`topic_id` 
WHERE `posts`.`user_id` = 1

, если бы user.topic_ids.where(:id=>topics.map(&:id)) был возможен, он бы сгенерировал это:

SELECT topics.id FROM `topics` 
INNER JOIN `posts` ON `topics`.`id` = `posts`.`topic_id` 
WHERE `posts`.`user_id` = 1 AND `topics`.`id` IN (...)

это точно такой же запрос, который генерируется с помощью user.topics.select("topics.id").where(:id=>topics.map(&:id))

, в то время как user.posts.select(:topic_id).where(:topic_id=>topics.map(&:id)) генерирует следующий запрос:

SELECT topic_id FROM `posts` 
WHERE `posts`.`user_id` = 1 AND `posts`.`topic_id` IN (...)

, какой из этих двух вариантов более эффективен, зависит от данных в реальных таблицах и заданных индексах (и какой дБ используется).

Если список идентификаторов тем для пользователя длинный и содержит многократные повторения тем, имеет смысл сгруппировать идентификаторы тем на уровне запроса:

user.posts.select(:topic_id).group(:topic_id).where(:topic_id=>topics.map(&:id))
2 голосов
/ 22 декабря 2011

Предположим, что ваша модель темы имеет столбец с именем id, вы можете сделать что-то вроде этого

Topic.select(:id).join(:posts).where("posts.user_id = ?", user_id)

Это выполнит только один запрос к вашей базе данных и даст вам все идентификаторы тем, которые имеют сообщения для данного идентификатора пользователя

...