我试图通过LocationFeature模型为具有位置和功能的网站构建过滤系统。基本上它应该做的是根据特征ID的组合给我所有的位置。
例如,如果我调用方法:
Location.find_by_features(1,3,4)
它只应返回具有所选功能的全部的位置。因此,如果某个位置具有feature_ids [1,3,5],则不应返回,但如果它具有[1,3,4,5],则应该。但是,目前它给了我 的地点。因此,在此示例中,它返回两者,因为每个feature_id中都存在一些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})
但这只是给了我feature_id为1的所有位置。
查询匹配所有请求功能的位置的最佳方法是什么?
答案 0 :(得分:2)
当你做一个包含它会产生一个&#34;伪表&#34;在内存中具有表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位于所有数组中:(i& #39;已将您的方法重命名为更具描述性的方法)
#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:参数中*ids
中的星号表示它会将一个参数列表(包括一个参数,就像一个&#34;一个&#34;列表)转换为一个数组
注2:inject
是一个方便的设备。它说&#34;在数组中的第一个和第二个元素之间执行此代码,然后在此结果与第三个元素之间,然后是 this 和第四个元素等的结果,直到你走到了尽头。在这种情况下,在每对(a和b)中的两个元素之间进行的代码是&#34;&amp;&#34;在处理数组时,它是&#34; set intersection运算符&#34; - 这将仅返回两对中的元素。当你通过这样做的数组列表时,只有所有数组中的元素才能存活下来。这些是与所有给定特征相关联的位置ID。
编辑:我确定有一种方法可以使用单个SQL查询执行此操作 - 可能使用group_concat
- 其他人很可能会很快发布:)
答案 1 :(得分: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)
}