Rails:如何强制 ActiveRecord 每次都为关联生成别名(就像 Java 中的 Hibernate 那样),而不仅仅是当它有歧义时?

问题描述 投票:0回答:1

我在一个项目上工作,其中有 STI

Item
和 5 个子类(
Item1
Item2
...
Item5
)。此 STI(
items
表)映射到连接表
item_parents
Parent
记录(
parents
表)记录。映射是通过
has_many trough:
完成的。

每一项都有两个字段:

name
code
都是字符串。
Parent
有很多字段,但为了举例,我们假设它有
name
,
created_at
。 在前端,它们显示在一个表中,如下所示:

Parent.name | Parent.created_at | Item1.name | Item1.code | Item2.name | Item2.code | ...

用户可以为每一列配置过滤。它可以是任何组合或根本没有过滤器。例如,他们可以选择以下组合:

Parent.created_at before 2020.02.22
Item1.name containing 'abc'
Item2.name containing 'xyz'
Item3.code equals 'Z12'

过滤代码是这样实现的:

def search(filters)
  filters.reduce(Parent.all) { |query, (key, value)| apply_filter(query, key, value) }
end

def apply_filter(query, key, value)
  case filter_key
  when :parent_name_contains
    query.where(Parent.arel_table[:name].matches("%#{value}%"))
  when :parent_created_at_before
    query.where(Parent.arel_table[:created_at].lt(value))
  when :item1_name_contains
    query.joins(:item1s).where(Item1.arel_table[:name].matches("%#{value}%"))
  when :item2_name_contains
    query.joins(:item2s).where(Item2.arel_table[:name].matches("%#{value}%"))
  when :item1_code_equals
    query.joins(:item1s).where(Item1.arel_table[:name].eq(value))
  when :item2_code_equals
    query.joins(:item2s).where(Item2.arel_table[:name].eq(value))
  # ... and so on for all the filters
  else
    query
  end
end

问题

当我通过

Item
的两个或多个不同子类的字段进行查询时,ActiveRecord 无法生成正确的
WHERE
子句。它不使用它在
JOIN
子句中为关联分配的别名。

假设我想按

Item1.name = 'i1'
Item2.name = 'i2'
过滤,那么rails生成的是这样的:

SELECT "parents".*
FROM "parents"
         INNER JOIN "item_parents"
                    ON "item_parents"."parent_id" = "parents"."id"
         INNER JOIN "items"
                    ON "items"."id" = "item_parents"."item_id"
                        AND "items"."item_type" = 'Item::Item1'

         INNER JOIN "item_parents" "item_parents_parents_join"
                    ON "item_parents_parents_join"."parent_id" = "parents"."id"
         INNER JOIN "items" "item2s_parents" -- OK. join has an alias
                    ON "item2s_parents"."id" = "item_parents_parents_join"."item_id"
                        AND "item2s_parents"."item_type" = 'Item::Item2'

WHERE "items"."name" = 'i1' 
  AND "items"."name" = 'i2' -- Wrong! Must be "item2s_parents"."name" = 'i2'

结果,我返回了零行,因为不可能同时有一个名称等于

'i1'
'i2'
的项目。

我试过的

编写自定义

joins_item
方法似乎是个好主意,它可以挖掘
query
并检查它之前是否有其他
joins
调用它(AR 将此类信息存储在
query.values[:joins]
query.values[:left_outer_joins]
) 如果有,那么它将返回另一个具有正确别名的
Arel::Table
实例。如果之前没有加入任何东西,那么我不需要别名并返回默认
Arel::Table
.

但是后来我发现AR在构建SQL的那一刻就解析了别名。因此,即使我在加入时可以猜到正确的别名(或没有别名),它最终也会改变。这实际上是当你先做

left_outer_joins
然后做
joins
时发生的事情。在生成的 SQL 中,AR 总是将
INNER JOIN
s 放在
LEFT OUTER JOIN
s 之前。

所以问题是...

当我使用 Arel 或任何其他或多或少可维护的解决方法/修复/猴子补丁时,有没有办法强制 AR 对所有内容进行别名处理?

    
我没有答案,我也没有足够的声誉来发表评论,但我目前正在努力解决这个确切的问题并且尚未找到有用或可维护的解决方案。经过一番挖掘,我确实确认添加引用关联的 

joins
ruby-on-rails ruby activerecord arel sti
1个回答
0
投票
left_outer_joins

以关联名称作为别名,而不是无别名或基于表名计算的别名。我遇到的问题是我不想打一个不必要的

where
电话只是为了确保我知道从协会建立的
JOIN
的别名。连接的范围已经在模型上定义,明确写出一个完整的
where
语句作为一个字符串,这样我就可以确保它有一个一致的别名似乎不是正确的解决方案,但我不确定是什么其他事情要做。
    

© www.soinside.com 2019 - 2024. All rights reserved.