链接同一命名范围的多个实例

时间:2015-03-08 15:39:31

标签: ruby-on-rails activerecord inner-join

我有一个名为Item,ItemTag和Tag的模型的Rails应用程序。项目通过ItemTags有很多标签。协会正常运作。

我想为允许按多个标签过滤的Items构建搜索界面。我有一堆针对各种条件的命名范围,它们没有问题链。我想我会根据用户输入构建查询。所以我创建了一个范围,它返回具有给定标记的Items集:

scope :has_tag, -> (tag_id) { joins(:tags).where(tags: {id: tag_id}) }

有效!

>  Item.has_tag(73).count
=> 6

>  Item.has_tag(81).count
=> 5

但是:

> Item.has_tag(73).has_tag(81).count
=> 0

有两个项目同时包含两个标记,但将范围链接在一起会产生以下SQL,由于显而易见的原因,它总是返回空白;它正在寻找一个有两个ID的标签,而不是一个有两个标签的项目。

SELECT [items].* 
FROM [items] 
INNER JOIN [item_tags] ON [item_tags].[item_id] = [items].[id] 
INNER JOIN [tags] ON [tags].[id] = [item_tags].[tag_id] 
WHERE [tags].[id] = 81 AND [tags].[id] = 73

我知道我们可以在它们返回后获得集合的交集,但这似乎效率低下,所以我想知道这里是否有标准做法。 (这似乎是一项常见任务。)

>  (Item.has_tag(73) & Item.has_tag(81)).count
=> 2

有没有办法编写范围来使其工作,还是需要以另一种方式完成?

1 个答案:

答案 0 :(得分:1)

我认为输入标签需要作为数组处理,而不是链接。使用此语句,您将获得标记(71)的标记(83)下的子集,这不是您想要的。查询证实了这一点。

Item.has_tag(73).has_tag(81).count

您可以尝试将标记作为数组处理的范围,然后使用lambda

构造查询
 scope :has_tags, lambda { |tag_ids| includes(:tag_ids)
    .where("tag_id IN (?)", tag_ids.collect { |tag_id| } )
    .group('"items"."id"')
    .having('COUNT(DISTINCT "tag_ids"."id") = ?', tag_ids.count) }

我不完全了解您的模型关系设置等,而且我是错字女王,所以上面的代码段可能开箱即用,但我认为这里的信息是肯定的,您可以在单个范围内处理一组标记并查询一次以获取集合。