链接Rails 3范围在has_many中通过关联

时间:2011-08-09 07:21:01

标签: ruby-on-rails scope associations chaining kaminari

这可行吗?

我有以下范围:

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进行分页,这只适用于关系而不是数组 - 所以我需要返回一个范围。

3 个答案:

答案 0 :(得分:2)

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

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

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

因此,将所有Tag加入一个特定的Thing,它会为每个Tag - Thing组合生成一行(如果您不相信,只需运行这个SQL语句的COUNT(*))。 问题是您查询的条件如下所示:WHERE (tags.name = 'test_tag_1') AND (tags.name = 'test_tag_2')将针对每一行进行检查,永远不会成立。 tags.name无法同时等同test_tag_1test_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布尔搜索。如果您愿意,我会为您提供有关此解决方案的更多详细信息。

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

答案 1 :(得分: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)。我希望这是一个有用的思考这个问题的方法。

答案 2 :(得分:0)

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