1

这是可行的吗?

我有以下范围:

class Thing < ActiveRecord::Base

scope :with_tag, lambda{ |tag| joins(:tags).where('tags.name = ?', tag.name)
                                           .group('things.id') }

def withtag_search(tags)
  tags.inject(scoped) do |tagged_things, tag|
    tagged_things.with_tag(tag) 
  end
end

如果传入的标签数组中有一个标签,我会得到一个结果,Thing.withtag_search(array_of_tags)但是如果我在该数组中传递多个标签,我会得到一个空关系作为结果。如果有帮助:

Thing.withtag_search(["test_tag_1", "test_tag_2"])

SELECT "things".* 
FROM "things" 
INNER JOIN "things_tags" ON "things_tags"."thing_id" = "things"."id" 
INNER JOIN "tags" ON "tags"."id" = "things_tags"."tag_id" 
WHERE (tags.name = 'test_tag_1') AND (tags.name = 'test_tag_2') 
GROUP BY things.id

=> [] # class is ActiveRecord::Relation

然而

Thing.withtag_search(["test_tag_1"])

SELECT "things".* 
FROM "things" 
INNER JOIN "things_tags" ON "things_tags"."thing_id" = "things"."id" 
INNER JOIN "tags" ON "tags"."id" = "things_tags"."tag_id" 
WHERE (tags.name = 'test_tag_1') 
GROUP BY things.id

=> [<Thing id:1, ... >, <Thing id:2, ... >] # Relation including correctly all 
                                            # Things with that tag

我希望能够将这些关系链接在一起,以便(除其他原因外)我可以使用 Kaminari gem 进行分页,它只适用于关系而不是数组 - 所以我需要返回一个范围。

4

3 回答 3

2

我也遇到了这个问题。问题不是 Rails,问题肯定是 MySQL:

您的 SQL 将创建以下临时 JOIN 表(仅显示必要的字段):

+-----------+-------------+---------+------------+
| things.id | things.name | tags.id | tags.name  |
+-----------+-------------+---------+------------+
|     1     |     ...     |    1    | test_tag_1 |
+-----------+-------------+---------+------------+
|    1      |     ...     |    2    | test_tag_2 |
+-----------+-------------+---------+------------+

因此,将所有Tags 连接到一个特定的 s Thing,而是为每个组合生成一行TagThing如果您不相信,只需COUNT(*)在此 SQL 语句上运行)。问题是您的查询条件看起来像这样:WHERE (tags.name = 'test_tag_1') AND (tags.name = 'test_tag_2')将针对每一行进行检查,并且永远不会是真的。不可能tags.name同时test_tag_1相等test_tag_2

标准的 SQL 解决方案是使用 SQL 语句INTERSECT……但不幸的是,不使用 MySQL。

最好的解决方案是Thing.withtag_search为每个标签运行,收集返回的对象,并仅选择每个结果中包含的对象,如下所示:

%w[test_tag_1 test_tag_2].collect do |tag|
  Thing.withtag_search(tag)
end.inject(&:&)

如果你想把它作为一个ActiveRecord关系,你可以这样做:

ids = %w[test_tag_1 test_tag_2].collect do |tag|
  Thing.withtag_search(tag).collect(&:id)
end.inject(&:&)
Things.where(:id => ids)

另一个解决方案(我正在使用)是缓存Thing表中的标签,并对其进行 MySQL 布尔搜索。如果您愿意,我将为您提供有关此解决方案的更多详细信息。

无论如何,我希望这会对你有所帮助。:)

于 2011-08-12T21:26:14.177 回答
0

乍一看这相当复杂,但根据您的 SQL,您需要:

WHERE (tags.name IN ('test_tag_1', 'test_tag_2'))

我对 Rails 3 的处理不多,但是如果您可以适当地调整您的 JOIN,这应该可以解决您的问题。您是否尝试过类似的解决方案:

 joins(:tag).where('tags.name IN (?), tags.map { |tag| tag.name })

这样,您将以您期望的方式加入(UNION 而不是 INTERSECTION)。我希望这是思考这个问题的有用方法。

于 2011-08-09T09:06:58.700 回答
0

似乎无法找到解决此问题的方法。因此,我没有使用 Kaminari 并滚动我自己的标记,而是切换到 Acts-as-taggable-on 和 will-paginate

于 2011-08-12T20:51:38.793 回答