2011-08-09 105 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和滾動我自己的標籤我已經切換到行爲作爲標籤,並將分頁