2011-08-09 55 views
1

这是可行吗?链接导轨3通过关联has_many范围3

我有以下范围:

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 

我希望能够来链接这些关系在一起,以便(除其他原因),我可以用雷宝石的分页这仅适用于关系不是数组 - 所以我需要返回一个范围。

回答

2

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

你的SQL将创建下列临时JOIN表(只neccesary域显示):

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

因此,而不是加入所有Tag s到一个特定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的布尔搜索。如果你愿意,我会给你更详细的解决方案。

反正我希望这会帮助你。 :)

+0

干杯 - 这看起来很有帮助,并感谢识别问题...我会尝试上述 –

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)。我希望这是考虑这个问题的有用方法。

+0

谢谢我会试试这个:-) –

+0

试过了。不,这给了我所有的东西与所有的东西标签不是两个标签:-(至少说它是返回一个范围,这对我想要通过任何输入标签进行搜索的功能是有用的 - 所以感谢那:-)现在我需要看看我是否可以做到这一点,以便我可以得到一个范围,让我缩小必须让所有的标签进入......任何想法? –

+0

哦,铁轨是结合你的JOINs?如何让连接独特超越Rails将检测到的?而不是连接(:标签),尝试连接('JOIN标签t1 ON t1.id = things_tags.id WHERE t1.name =#{tag.name}')。再次,我一直在使用Rails 2而不是3,所以我无法获得完美的语法。用手指定连接肯定会完成这个难题! – ghayes

0

似乎无法找到解决此问题的方法。所以,而不是使用Kaminari和滚动我自己的标签我已经切换到行为作为标签,并将分页