您想要做的是将所有捐赠加入他们的类别和与该类别相关的作物,然后过滤与给定字符串匹配的类别名称或作物名称。让我们从 SQL 中开始:
SELECT d.*
FROM donations d
JOIN categories ca ON ca.id = d.category_id
JOIN categories_crops cc ON cc.category_id = ca.id
JOIN crops cr ON cr.id = cc.crop_id
WHERE
ca.name LIKE 'foo%'
OR
cr.name LIKE 'foo%'
所以假设您的数据集如下所示:
donations categories crops categories_crops
---------------- ------------------ ------------ ---------------------
id | category_id id | name id | name crop_id | category_id
================ ================== ============ =====================
1 | 1 1 | leafy greens 1 | lettuce 1 | 1
2 | 1 2 | dark greens 2 | broccoli 2 | 2
3 | 2 3 | kale 3 | 1
3 | 2
上面的 JOIN 将生成一个看起来像这样的预过滤集,为了更容易表示而进行了精简:
donations X categories X categories_crops X crops
-------------------------------------------------
donation_id | category.name | crops.name
=================================================
1 | leafy greens | lettuce
1 | leafy greens | kale
2 | leafy greens | lettuce
2 | leafy greens | kale
3 | dark greens | broccoli
3 | dark greens | kale
如您所见,每个捐赠 ID 显示在其捐赠类别下的所有作物旁边(请注意,此时,我不确定您的数据模型是否正确。无论哪种方式,我所描述的原则都应该对您和其他人有所帮助)。因此,如果我们按 leafy 过滤,我们将取回捐赠 1 和 2。如果我们按 kale 过滤,我们将取回捐赠 1、2 和 3 .
希望一切都说得通,我们可以进入最后一步——将 SQL 转换为 ActiveRecord。很直接:
Donations
.joins(category: :crops)
.where('categories.name LIKE :term OR crops.name LIKE :term', term: "%#{search_term}%")
join 可能需要为category: { categories_crops: :crops }) 或其他名称,具体取决于您的关系是如何设置的。这个想法是遍历您定义的关系以构建如上所述的连接。
编辑: 根据 OP 在 cmets 中的说明,数据模型还包含隐含的类别层次结构,从作物 -> 类别关系推断,但用作物名称替换同名类别。隐式关系,需要额外的逻辑,但在数据库设计中并不明显,可能很难查询——这通常表明数据模型可能需要更新。但让我们从更新查询开始。
我们还需要包含“属于”正在搜索的类别的类别,因此我们需要一个额外的连接。由于不是每个类别都有父类别,因此应该是外连接。然而,加入标准会很奇怪。我的第一个想法是创建一个临时表,将类别与其父类别名称连接起来,然后将连接留在上面。于是查询变为:
SELECT d.*
FROM donations d
JOIN categories ca ON ca.id = d.category_id
JOIN categories_crops cc ON cc.category_id = ca.id
JOIN crops cr ON cr.id = cc.crop_id
LEFT JOIN (
SELECT ca2.name as parent_category_name, ca3.id as child_category_id
FROM categories_crops cc2
JOIN categories ca2 ON ca2.id = cc2.category_id
JOIN crops cr2 ON cr2.id = cc2.crop_id
JOIN categories ca3 ON ca3.name = cr2.name
) cp
ON cp.child_category_id = ca.id
WHERE
ca.name LIKE 'foo%'
OR
cr.name LIKE 'foo%'
OR
cp.parent_category_name LIKE 'foo%'
翻译成 Rails:
Donations
.joins(category: :crops)
.joins(<<-SQL)
LEFT JOIN (
SELECT ca2.name as parent_category_name, ca3.category_id as child_category_id
FROM categories_crops cc2
JOIN categories ca2 ON ca2.id = cc2.category_id
JOIN crops cr2 ON cr2.id = cc2.crop_id
JOIN categories ca3 ON ca3.name = cr2.name
) cp ON cp.child_category_id = categories.id
SQL
.where('categories.name LIKE :term OR crops.name LIKE :term OR cp.parent_category_name LIKE :term', term: "%#{search_term}%")