在 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 < PartyName; ALIAS = "expired_names_party".freeze; end
,在我的有效 lambda 中:table = defined?(self::ALIAS) ? self::ALIAS : table_name
。这并不适用于所有情况,但这是一个好的开始。谢谢!
哦,在派对上:has_many :expired_names, -> invalid , class_name: 'ExpiredName', foreign_key: :partyId, inverse_of: :party,
感谢您的解释。我不太明白你在做什么,但现在我想我明白了。我更新了我的仓库中的代码来测试情况。似乎数据库足够聪明,可以为每个连接语句使用正确的 party_names 表,即使它没有使用别名。不过,我确实需要将Party.joins(:name, :expired_names)
更改为Party.left_joins(:name, :expired_names)
。否则,除非他们至少有一个有效名称和一个过期名称,否则您将不会让任何一方回来。让我知道这是否对您有帮助。以上是关于在 Rails 范围内获取动态表别名的主要内容,如果未能解决你的问题,请参考以下文章