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

时间:2014-10-01 14:20:20

标签: ruby-on-rails ruby ruby-on-rails-4

我试图通过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的所有位置。

查询匹配所有请求功能的位置的最佳方法是什么?

2 个答案:

答案 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)
}