RubyOnRails多个范围组合

时间:2015-09-02 13:41:25

标签: ruby-on-rails scopes named-scopes

我有一个带有has_many Types表的产品模型和几个范围:

class Product < ActiveRecord::Base

  has_many :product_types
  has_many :types, through: :product_types

  scope :type1, -> { joins(:types).where(types: { name: "Type1" }) }
  scope :type2, -> { joins(:types).where(types: { name: "Type2" }) }

end

当我尝试使用一个范围(例如Product.type1)时,一切顺利,但一次两个范围(Product.type1.type2)返回一个空查询。是的,一种产品可能有多种类型。

最终目标是按类型创建具有复选框形式的产品过滤器。当我检查type1和type2时,我想要看到我的所有产品同时具有Type1和Type1。

UPD 1

所以我试图做几个查询,然后&amp;他们像@ aaron.v建议的那样。我想在函数内部执行逻辑:

def products_by_type_name(types)
  all_types = types.each { |type| Product.joins(:types).where(types: { name: type }).distinct }
  ...
end

我的观点是迭代每种类型,收集所有产品,然后&amp;他们在功能里面。 问题是当我迭代时,每个循环返回字符串而不是哈希数组。

Product.joins(:types).where(types: { name: types }).distinct # returns array of hashes, it's okay.

types.each { |type| Product.joins(:types).where(types: { name: type }).distinct } # each loop returns string (Type name) instead of array of hashes.

我做错了什么?

解决方案1 ​​

建议@ aaron.v,解释如下

def self.by_type_name(types)
  product_ids = []
  types.each do |t|
    product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
  end
  find(product_ids.inject(:&))
end

解决方案2

在reddit上找到。 在此函数中,您将获取至少具有一个必需类型的所有产品,而不是仅对具有所需类型数量的产品进行分组。因此,您只能同时获得属于每种类型的产品。

def self.by_type_name(types)
    joins(:types).where(types: { name: types }).distinct.group('products.id').having('count(*) = ?', types.each.count) 
end

1 个答案:

答案 0 :(得分:0)

如果您有数据库背景,那么为什么您根据编写示波器的方式无法找到多种类型的产品,这将非常明显。

从这些范围编写的数据库查询将乘以行以确保具有多种类型的产品将为每种类型具有不同的行。组合两个范围时的查询是写

where `types`.name = "Type1" AND `types`.name = "Type2"

这不会发生,因为列不会在连接上添加同一行的倍数。

你想要做的是有一个方法,你可以传递一个类型名称数组来找到它

def self.by_type_name(types)
  joins(:types).where(types: { name: types }).distinct
end

此方法可以单独接受一个名称,也可以接受要查找的类型的名称数组

如果您传递类型名称数组,如[&#34; type1&#34;,&#34; type2&#34;],则会产生类似

的查询
where `types`.name in ("type1", "type2")

然后你应该看到你期望的结果

更新

为了修改您在寻找所有类型的产品时所需的内容,我决定这样做

def self.by_type_name(types)
  product_ids = []
  types.each do |t|
    product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
  end
  find(product_ids.inject(:&))
end

此方法需要一个类型名称数组(即使它是一种类型,也传递一个类型的数组)。它将做的是找到具有一个特定类型名称的所有产品,只选择产品ID(在数据库上将更轻)并将其映射到一个数组,该数组将被转储到product_ids数组中。在它循环遍历每个类型名称后,它将找到在每个数组中相交的所有产品,其中包含传入所有类型的产品。