RAIRS:如何强制Activerecord每次都会产生一个关联的别名(就像Java中的Hibernate一样),不仅是在模棱两可的时候吗?

发布于 2025-02-08 06:23:15 字数 3404 浏览 3 评论 0原文

我在一个项目上工作,其中有5个子类(item1item2 ... item5)的项目。 。此STI(<代码>项目表)是通过JOIN表item_parents to parent> parent record(父母表)记录的。映射是通过has_many槽进行的:

每个项目都有两个字段:name,<代码>代码都是字符串。 parent有很多字段,但是为了示例,可以说它具有namecreate_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 JOINs before LEFT OUTER JOINs 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文