Rails查询has_many:通过有条件地使用多个ID

问题描述:

我试图通过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

我会做一组子查询。如果你愿意的话,你实际上也可以把它作为范围。

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

当你做一个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