在 Rails 范围内获取动态表别名

Posted

技术标签:

【中文标题】在 Rails 范围内获取动态表别名【英文标题】:Get dynamic table alias in Rails scope 【发布时间】:2018-06-06 17:38:25 【问题描述】:

我的大多数模型共享两个范围。他们有直接引用模型表名的原始 SQL,这与 Arel 不能很好地配合:

class ApplicationRecord < ActiveRecord::Base
  valid = lambda do |positive = true|
    if %w[validForBegin validForEnd].all?  |c| base_class.column_names.include?(c) 
      condition = "NOW() BETWEEN #base_class.table_name.validForBegin AND #base_class.table_name.validForEnd"
      condition = "!(#condition)" unless positive
      where(condition)
    end
  end

  scope :valid, valid
  scope :invalid, ->  valid(false) 
end

# Sample usage

class Party < ApplicationRecord
  has_one :name,
    ->  valid ,
    class_name: 'PartyName',
    foreign_key: :partyId,

  has_many :expired_names,
    ->  invalid ,
    class_name: 'PartyName',
    foreign_key: :partyId,
end

由于我的范围直接指向模型的table_name,我不能同时加入这两个关联:

Party.joins(:name, :expired_names).first

# Produces this sequel statement

SELECT `party`.*
FROM `party`
INNER JOIN `party_name` ON `party_name`.`partyId` = `party`.`id`
AND (NOW() BETWEEN party_name.validForBegin AND party_name.validForEnd)
INNER JOIN `party_name` `expired_names_party` ON `expired_names_party`.`partyId` = `party`.`id`
AND (!(NOW() BETWEEN party_name.validForBegin AND party_name.validForEnd))
ORDER BY `party`.`id` ASC LIMIT 1

请注意,连接上的两个“AND”条件均指表party_name。第二个应该引用expired_names_party,动态生成的表别名。对于更复杂的 Rails 查询,其中 Arel 为每个表分配别名,两个连接都将失败。

我的作用域是否可以使用 Arel 在执行时分配给它的别名

【问题讨论】:

【参考方案1】:

我创建了这个 repo 来帮助测试你的情况: https://github.com/wmavis/so_rails_arel

我认为问题在于您正在尝试对两种关系使用相同的类。默认情况下,rails 希望使用与该类关联的表的类名称。因此,它对两个查询都使用表名party_name

为了解决这个问题,我创建了一个ExpiredName 类,它继承自PartyName,但告诉rails 使用expired_names 表: https://github.com/wmavis/so_rails_arel/blob/master/app/models/expired_name.rb

class ExpiredName < PartyName
  self.table_name = 'expired_names'
end

这似乎解决了我的代码中的问题:

Party.joins(:name, :expired_names).to_sql
 => "SELECT \"parties\".* FROM \"parties\" 
INNER JOIN \"party_names\" 
  ON \"party_names\".\"party_id\" = \"parties\".\"id\" 
INNER JOIN \"expired_names\" 
  ON \"expired_names\".\"party_id\" = \"parties\".\"id\""

如果它不适合你,请告诉我,我会尽力提供帮助。

【讨论】:

感谢您的帮助!这不会完全起作用,因为 ExpiredName 使用与 PartyName 相同的表。然而,它确实让我得到了一个有效的概念证明:class ExpiredName &lt; PartyName; ALIAS = "expired_names_party".freeze; end,在我的有效 lambda 中:table = defined?(self::ALIAS) ? self::ALIAS : table_name。这并不适用于所有情况,但这是一个好的开始。谢谢! 哦,在派对上:has_many :expired_names, -&gt; invalid , class_name: 'ExpiredName', foreign_key: :partyId, inverse_of: :party, 感谢您的解释。我不太明白你在做什么,但现在我想我明白了。我更新了我的仓库中的代码来测试情况。似乎数据库足够聪明,可以为每个连接语句使用正确的 party_names 表,即使它没有使用别名。不过,我确实需要将Party.joins(:name, :expired_names) 更改为Party.left_joins(:name, :expired_names)。否则,除非他们至少有一个有效名称和一个过期名称,否则您将不会让任何一方回来。让我知道这是否对您有帮助。

以上是关于在 Rails 范围内获取动态表别名的主要内容,如果未能解决你的问题,请参考以下文章

使用单表继承时是不是可以在 Rails 3 中有动态 id 列

abap动态内表获取字段名

PostgreSQL-9-别名与动态表复制

在配置单元窗口范围内使用表列

使用公式在动态变化的范围内返回匹配单元格的值?

使用引导进度条的 Rails 动态数据进度条