RAIRS:如何强制Activerecord每次都会产生一个关联的别名(就像Java中的Hibernate一样),不仅是在模棱两可的时候吗?
我在一个项目上工作,其中有5个子类(item1
,item2
... item5
)的项目。 。此STI(<代码>项目表)是通过JOIN表item_parents
to parent> parent
record(父母
表)记录的。映射是通过has_many槽进行的:
。
每个项目都有两个字段:name
,<代码>代码都是字符串。 parent
有很多字段,但是为了示例,可以说它具有name
,create_at_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 item
的字段时,ActivereCord未能生成正确的,其中< /代码>子句。它不使用
join> 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
并检查它是否具有其他JONING> JONINS
在此之前进行调用(AR将此类信息存储在query.values [:JONINS]
和query.values [:left_outer_joins]
),如果有的话,它将返回另一个Arel :: Table
实例具有正确的别名。如果以前没有加入,那么我不需要别名并返回默认arel :: table
。
但是后来我发现AR在建造SQL时解决了别名。因此,即使我可以猜到加入时的正确别名(或没有别名)也可能会改变。实际上,当您首先进行left_outer_joins
然后加入
时,实际上会发生这种情况。 AR总是在左OUT OUT JOIN
s之前,将内部加入
s。
因此,问题是...
当我进行加入
或left_outer_joins
时,是否有一种方法可以强迫AR来使AR驱动所有内容/此问题的猴子补丁?
I work on a project where there is STI Item
with 5 subclasses (Item1
, Item2
... Item5
). This STI (items
table) is mapped over a join table item_parents
to Parent
record (parents
table) record. The mapping is done via has_many trough:
.
Each of the items has two fields: name
, code
both are strings. Parent
has many fields, but for the sake of example let's say it has name
, created_at
.
On the frontend, they are displayed in one table, like this:
Parent.name | Parent.created_at | Item1.name | Item1.code | Item2.name | Item2.code | ...
Users can configure filtering for each of the columns. It can be any combination or no filter at all. For example, they can choose the following combination:
Parent.created_at before 2020.02.22
Item1.name containing 'abc'
Item2.name containing 'xyz'
Item3.code equals 'Z12'
The filtering code implemented like this:
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
The problem
When I query by fields of two or more different subclasses of Item
, ActiveRecord fails to generate correct WHERE
clause. It does not use the alias that it has assigned for the association in JOIN
clause.
Let's say I want to filter by Item1.name = 'i1'
and Item2.name = 'i2'
, then what rails generates is this:
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'
As a result, I have zero rows returned, because it's impossible to have an item with name equal to 'i1'
AND 'i2'
at the same time.
What I tried
It seemed to be a good idea to write a custom joins_item
method, that would dig the query
and check whether it has other joins
called on it before (AR stores such information in query.values[:joins]
and query.values[:left_outer_joins]
) and if there is, then it would return another Arel::Table
instance having the correct alias. If there is nothing joined before, then I don't need alias and return the default Arel::Table
.
But then I found out that AR resolves aliases at the moment of building SQL. So even though I could guess the correct alias (or no alias) at the moment of joining it can change in the end. And this is actually what happens when you do left_outer_joins
first and then joins
. AR always places INNER JOIN
s before LEFT OUTER JOIN
s in the resulting SQL.
So the question is...
Is there a way to force AR to alias everything when I do joins
or left_outer_joins
with Arel, or any other more or less maintainable workaround/fix/monkey patch for this issue?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论