我有一个名为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
有没有办法编写范围来使其工作,还是需要以另一种方式完成?
答案 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) }
我不完全了解您的模型关系设置等,而且我是错字女王,所以上面的代码段可能开箱即用,但我认为这里的信息是肯定的,您可以在单个范围内处理一组标记并查询一次以获取集合。