2014-10-01 51 views
2

我試圖通過LocationFeature模型爲具有位置和功能的網站構建過濾系統。基本上它應該做的是根據特徵ID的組合給我所有的位置。Rails查詢has_many:通過有條件地使用多個ID

因此,舉例來說,如果我調用的方法:

Location.find_by_features(1,3,4) 

應該只返回有所有的選擇功能的位置。所以如果一個位置有feature_ids [1,3,5] 不應該得到返回,但如果它有[1,3,4,5] 應該。但是,目前它給我的位置有或者其中的。因此在這個例子中它返回了兩個,因爲其中每個feature_ids都有一些feature_ids。

這裏是我的模型:

class Location < ActiveRecord::Base 
    has_many :location_features, dependent: :destroy 
    has_many :features, through: :location_features 

    def self.find_by_features(*ids) 
    includes(:features).where(features: {id: ids}) 
    end 
end 

class LocationFeature < ActiveRecord::Base 
    belongs_to :location 
    belongs_to :feature 
end 

class Feature < ActiveRecord::Base 
    has_many :location_features, dependent: :destroy 
    has_many :locations, through: :location_features 
end 

顯然,這種代碼是不工作我想要的方式來和我不能讓我的頭周圍。我也試過這樣的東西,如:

Location.includes(:features).where('features.id = 5 AND features.id = 9').references(:features) 

但它只是什麼都沒有返回。用OR代替AND再給我。我也試過:

Location.includes(:features).where(features: {id: 9}, features: {id: 1}) 

但這只是給了我所有的1

的FEATURE_ID什麼是查詢匹配的所有請求的功能位置的最佳方式的位置?

+0

這是哪個dbms? Mysql,postgres,sqlite等? – 2014-10-01 14:34:08

回答

1

我會做一組子查詢。如果你願意的話,你實際上也可以把它作爲範圍。

scope :has_all_features, ->(*feature_ids) { 
    where((["locations.id in (select location_id from location_features where feature_id=?)"] * feature_ids.count).join(' and '), *feature_ids) 
} 
+0

這個作品非常漂亮!謝謝! – 2014-10-15 07:50:47

2

當你做一個include時,它會在內存中生成一個「僞表」,它具有表A和表B的所有組合,在這種情況下,這個組合在foreign_key上。 (在這種情況下,已經有一個包含連接表(feature_locations),使事情變得複雜)。

此表中不會有任何滿足條件features.id = 9 AND features.id = 1的行。每行只有一個features.id值。

我會爲此做的是忘記功能表:您只需要查看連接表location_features,以測試是否存在特定的feature_id值。我們需要一個查詢來比較此表中的feature_id和location_id。

一種方法是獲取功能,然後得到一個數組的集合,如果相關的location_ids(它只是調用連接表),然後看看哪些位置ID在所有的數組:(我已經改名方法是更具描述性的)

#in Location 
def self.having_all_feature_ids(*ids) 
    location_ids = Feature.find_all_by_id(ids).map(&:location_ids).inject{|a,b| a & b} 
    self.find(location_ids) 
end 

注1:在PARAMS在*ids星號意味着它將參數列表(包括一個參數,它是像一個「一個的列表」)轉換成一個單一的陣列。

注2:inject是一個方便的設備。它說「在數組中的第一個元素和第二個元素之間執行此代碼,然後在此結果與第三個元素之間執行此代碼,然後執行這個和第四個元素等的結果,直到您結束爲止。在這種情況下,我在每對(a和b)中的兩個元素之間執行的代碼是「&」,在處理數組時,它是「集合交集運算符」 - 這將只返回兩對中的元素。在你完成這個數組列表的時候,只有在所有數組中的元素才能存活下來。這些是與所有給定功能相關聯的位置的ID。

編輯:我敢肯定有辦法用一個SQL查詢來做到這一點 - 可能使用group_concat - 這有人可能會發布不久:)

+0

謝謝,很好的解釋!但是,如果我嘗試這個,它給了我一個沒有方法錯誤的Feature.find_all_by_id - 任何想法爲什麼? – 2014-10-15 07:52:57

+1

對不起,這是一個Rails版本的東西:'find_all_by_'已經在Rails 4中被棄用了。用'Feature.where([「id in(?)」,ids])替換'Feature.find_all_by_id(id) – 2014-10-15 08:57:58