Rails 4 - 如何在活动记录查询中为includes() 和 joins() 提供别名

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

我如何给出别名,例如

includes()
? 以下给出:

  • 用户:活动记录模型
  • Student:活动记录模型,继承自User(STI)
  • Teacher:活动记录模型,继承自User(STI)
  • 项目:活动记录模型

这里有一些例子:

第一个案例(更多性传播感染协会)

Project.all.includes(:students, :teachers).order('teachers_projects.name ASC') # order on teachers
Project.all.includes(:students, :teachers).order('users.name ASC') # order on students

Rails 自动在 SQL 中为

teachers_projects
使用别名
:teachers
。我如何覆盖它,以便我可以在 SQL 中使用别名
teachers
而不是
teachers_projects
:students
获取别名
users

这个例子失败了:

Project.all.includes(:students, :teachers).order('teachers.name ASC')
Project.all.includes(:students, :teachers).order('students.name ASC')
Project.all.includes(:students, :teachers).order('students_projects.name ASC')

第二种情况(一个性传播感染协会)

如果我在方法

:students
中仅使用
:teachers
(不带
includes()
),Rails使用STI基类名称
users
(不附加
_projects
)的名称别名作为
:students

Project.all.includes(:students).order('users.name ASC') # order on students

这个例子失败了:

Project.all.includes(:students).order('students.name ASC')
Project.all.includes(:students).order('students_projects.name ASC')

问题

可能存在类似:

Project.all.includes(:students).alias(students: :my_alias)

RAILS 别名跟踪器

https://github.com/rails/rails/blob/v4.2.0/activerecord/lib/active_record/associations/alias_tracker.rb#L59

测试应用程序

https://gist.github.com/phlegx/add77d24ebc57f211e8b

https://github.com/phlegx/rails_query_alias_names

ruby-on-rails ruby-on-rails-3 ruby-on-rails-4 activerecord arel
3个回答
4
投票

Arel 实际上有一个别名方法。

student_alias = Student.arel_table.alias(:my_student_table_alias)

警告:这将要求您使用更多手工制作的 Arel 并手动进行连接。如果您不习惯 Arel 中的连接,它们可能会变得有点复杂。

student_alias_join = student_alias.create_on(
  Project.arel_table[:primary_key].eq(student_alias[:project_id])
)

Project.joins(
  Project.arel_table.create_join(
    student_alias, student_alias_join, Arel::Nodes::OuterJoin
  )
).order(...)

类似这样的事情应该可以做到。 当然,将其放入某个以

:my_student_table_alias
作为参数的类方法中会使其更加整洁和可重用,因为这在控制器中看起来有点混乱。


3
投票

我将采取另一种方法来解决这个问题:我不会尝试使用

.alias
方法来控制查询上的别名,而是让 Rails / Arel 处理这个问题,然后只查看正确的表名(别名或不)只要在范围内需要它就向上。

将此辅助方法添加到您的模型中,您可以从作用域调用该方法,以了解该作用域是否正在具有表名别名(同一个表上的多个联接)的

JOIN
中使用,或者是否另一方面,作用域没有表名的别名。

def self.current_table_name
  current_table = current_scope.arel.source.left

  case current_table
  when Arel::Table
    current_table.name
  when Arel::Nodes::TableAlias
    current_table.right
  else
    fail
  end
end

这使用

current_scope
作为基础对象来查找 arel 表。我在该对象上调用
source
来获取
Arel::SelectManager
,这反过来将为您提供
#left
上的当前表格。有两个选项:要么有一个
Arel::Table
(没有别名,表名位于
#name
上),要么有一个
Arel::Nodes::TableAlias
,其别名位于
#right
上。

现在您只需在您的

order
语句中使用它(未经测试):

Project.all.includes(:students, :teachers).order("#{current_table_name}.name ASC")
Project.all.includes(:students, :teachers).order("#{current_table_name}.name ASC")
Project.all.includes(:students, :teachers).order("#{current_table_name}.name ASC")

相关问题:


0
投票

我的用例是对数据库进行一些一致性检查,其中我将每个模型与我发现的每个关联连接起来并执行查询。

也许可以用 @neongrau 的解决方案解决一些问题,但通过关联会变得更加复杂。

@dgilperez 答案仅适用于范围,但我不知道如何在我的用例中应用它。

因为这不是一个经常运行的性能关键的事情,所以我选择了一个更基本的解决方案 -

#to_sql
上的正则表达式来查看最后一个连接是如何生成的。 /尽管我总是对正则表达式的实际速度感到惊讶/

last_alias = last_table_alias_from_sql(model.joins(association.name).select(:id).to_sql)
found = model.joins(association.name).where("#{last_alias}.field = #{model.arel_table.name}.field")

正则表达式查找器是这样的:

    def last_table_alias_from_sql(sql)
      matcher = /.*JOIN [`'"]([\S]+)[`'"] (?:[`'"]([\S]+)[`'"] )?ON/i.match(sql)
      matcher[2] || matcher[1]
    end

association
是我从
Model.reflect_on_all_associations
得到的东西,但如果你可以只使用任何
Symbol
,例如
:vehicles

决定将其作为针对奇怪用例的简单技巧。对于引号的内容,我尝试使其与数据库无关,但到目前为止仅在 MySQL 上进行了测试。您可能需要更改数据库的 RE。如果我记得我会在尝试其他答案时更新答案。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.