【问题标题】:How to filter association_ids for an ActiveRecord model?如何过滤 ActiveRecord 模型的关联 ID?
【发布时间】:2011-12-22 09:20:35
【问题描述】:

在这样的域中:

 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 读取所有主题ID,但我看不到将过滤条件应用于此方法的方法,因为它返回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 

但是无论输入集如何,这都会加载所有主题 ID。理想情况下,我想做类似的事情

 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,我正在使用 rails 3.0.x,但我也对 3.1 感到好奇。

我为什么要这样做?

基本上,我有一个半复杂搜索的结果页面(仅基于主题数据发生),我想将结果(主题)标记为用户交互的内容(写了一篇文章) .

所以,是的,还有另一种选择是加入 [Topic,Post] 以便结果是否标记为来自搜索,但这会破坏我缓存主题查询的能力(查询,即使没有连接,也比只为用户获取 id 更昂贵)

请注意上面概述的方法确实有效,他们只是觉得不够理想。

【问题讨论】:

  • 也许你可以描述你为什么要做你正在做的事情。有时,提高抽象会揭示需求的简化(士力架 vs 比利时巧克力)。

标签: ruby-on-rails ruby-on-rails-3 activerecord associations has-many


【解决方案1】:

我认为您的第二种解决方案几乎是最佳解决方案(从所涉及的查询的角度来看),至少就您想要使用的解决方案而言。

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 (...)

两者中哪一个更有效取决于定义的实际表和索引中的数据(以及使用哪个数据库)。

如果用户的主题 ID 列表很长并且主题重复多次,则在查询级别按主题 ID 分组可能是有意义的:

user.posts.select(:topic_id).group(:topic_id).where(:topic_id=>topics.map(&:id))

【讨论】:

  • 有趣的是,我没有认为连接是必要的,如果没有参照完整性和重复值,这是有道理的。帖子/选择/地图解决方案的问题在于,它在 ruby​​ 领域创建了更多对象(至少一个具有 8 个字段、4 个哈希、两个字符串的对象)只是为了给我一个 Fixnum,加上一个无用的循环,当他们是不必要的。这显然不是什么大不了的事,但感觉不对。
  • 是的,你是对的,但确实感觉不对,我的意思是 topic_ids 方法本身完全遵循主题/选择/地图路线。我能想到的避免实例化活动记录的唯一(丑陋)方法是直接执行 sql 查询...
【解决方案2】:

假设你的主题模型有一个名为 id 的列,你可以这样做

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

这将只对您的数据库运行一个查询,并为您提供所有主题 ID,这些 ID 包含给定 user_id 的帖子

【讨论】:

  • 但这不是我需要的:我已经有一个主题对象列表,我只想标记那些与用户有共同关系的帖子。
猜你喜欢
  • 1970-01-01
  • 2015-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多