RubyOnRails的多作用域组合



我得到了一个带有has_many Types表和几个作用域的Product模型:

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(例如type1))一切正常,但是一次两个作用域(Product.type1.type2)返回一个空查询。是的,一个产品可以有多种类型。

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

乌利希期刊指南1

所以我试着做了几个查询,然后&他们是@aaron。v显示。我想在函数内部执行逻辑,所以:

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

我的观点是遍历每种类型,收集所有产品,然后&它们在函数内部。问题是当我迭代时,每个循环返回字符串而不是哈希数组。

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建议。

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

如果您有数据库背景,那么很明显您无法根据编写作用域的方式找到具有多种类型的产品。

从这些范围写入的数据库查询将行相乘,以确保具有多种类型的产品对每种类型都有不同的行。当你合并两个作用域时,你的查询是写

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

这种情况永远不会发生,因为在连接中,列不会以同一行的倍数添加。

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

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

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

如果您传递一个类型名称的数组,如["type1", "type2"],这将导致类似

的查询
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(在DB上比较轻)并将其映射到一个数组中,该数组将被转储到product_ids数组中。在循环遍历每个类型名之后,它将找到在每个数组中相交的所有产品,从而得到传递了所有类型的产品。

最新更新