RubyOnRails 多作用域组合
RubyOnRails multiple scopes composition
我得到了一个具有 has_many 类型 table 和多个范围的产品模型:
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)returns 一个空查询。是的,一种产品可能有多种类型。
最终目标是使用复选框形式按类型创建产品过滤器。当我检查 type1 和 type2 时,我想同时查看所有具有 Type1 和 Type1 的产品。
UPD 1
所以我尝试做几个查询,然后按照@aaron.v 的建议进行 & 查询。我想在函数内部执行逻辑:
def products_by_type_name(types)
all_types = types.each { |type| Product.joins(:types).where(types: { name: type }).distinct }
...
end
我的意思是遍历每种类型,收集所有产品,然后在函数内 & 处理它们。
问题是当我迭代时,每个循环 returns 字符串而不是哈希数组。
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
如果您有数据库背景,这将很明显地说明为什么您无法根据您编写范围的方式找到具有多种类型的产品。
从这些范围编写的数据库查询将使行成倍增加,以确保具有多种类型的产品,每种类型都有一个不同的行。当您组合两个范围时,您的查询正在写
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
此方法需要一个类型名称数组(即使它是一种类型,也传递一个其中包含一种类型的数组)。它会做的是找到所有具有一个特定类型名称的产品,只有 select 产品 ID(这将在您的数据库中更轻)并将其映射到一个数组中,该数组将被转储到 product_ids 数组中.在遍历每个类型名称后,它会找到在每个数组中相交的所有产品,从而得到所有类型都已传入的产品。
我得到了一个具有 has_many 类型 table 和多个范围的产品模型:
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)returns 一个空查询。是的,一种产品可能有多种类型。
最终目标是使用复选框形式按类型创建产品过滤器。当我检查 type1 和 type2 时,我想同时查看所有具有 Type1 和 Type1 的产品。
UPD 1
所以我尝试做几个查询,然后按照@aaron.v 的建议进行 & 查询。我想在函数内部执行逻辑:
def products_by_type_name(types)
all_types = types.each { |type| Product.joins(:types).where(types: { name: type }).distinct }
...
end
我的意思是遍历每种类型,收集所有产品,然后在函数内 & 处理它们。 问题是当我迭代时,每个循环 returns 字符串而不是哈希数组。
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
如果您有数据库背景,这将很明显地说明为什么您无法根据您编写范围的方式找到具有多种类型的产品。
从这些范围编写的数据库查询将使行成倍增加,以确保具有多种类型的产品,每种类型都有一个不同的行。当您组合两个范围时,您的查询正在写
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
此方法需要一个类型名称数组(即使它是一种类型,也传递一个其中包含一种类型的数组)。它会做的是找到所有具有一个特定类型名称的产品,只有 select 产品 ID(这将在您的数据库中更轻)并将其映射到一个数组中,该数组将被转储到 product_ids 数组中.在遍历每个类型名称后,它会找到在每个数组中相交的所有产品,从而得到所有类型都已传入的产品。