别名来源 table
Aliasing the source table
有没有办法在单个范围的上下文中为源 table 添加别名?
我试过了:
scope = User.all
scope.arel.source.left.table_alias = "toto"
scope.where(firstname: nil) => "SELECT `toto`.* FROM `users` `toto` WHERE `toto`.`firstname` IS NULL"
问题是模型 class 保留了所有后续查询的别名:
User.all => "SELECT `toto`.* FROM `users` `toto`"
编辑
我把这个方法加到ApplicationRecord
def self.alias_source(table_alias)
klass = Class.new(self)
klass.all.source.left.table_alias = table_alias
klass
end
现在,我可以做到:
User.alias_source(:toto).where(firstname: nil) => "SELECT `toto`.* FROM `users` `toto` WHERE `toto`.`firstname` IS NULL"
我把这个方法添加到ApplicationRecord
def self.alias_source(table_alias)
klass = Class.new(self)
klass.all.source.left.table_alias = table_alias
klass
end
现在,我可以做到:
User.alias_source(:toto).where(firstname: nil) => "SELECT `toto`.* FROM `users` `toto` WHERE `toto`.`firstname` IS NULL"
由于创建从 ActiveRecord::Base
继承的 Anonymous 类 会导致内存膨胀,我不确定我是否会推荐它(参见此处:https://github.com/rails/rails/issues/31395)。
也许更好的实现方式是以块形式执行,例如将别名 table 生成到块然后再将其设置回去?
例如
def self.with_table_alias(table_alias, &block)
begin
self.all.source.left.table_alias = table_alias
block.call(self)
ensure
self.all.source.left.table_alias = nil
end
end
用法
User.with_table_alias(:toto) do |scope|
puts scope.joins(:posts).where(firstname: "Ruur").to_sql
end
# "SELECT `toto`.*
# FROM
# `users` `toto`
# INNER JOIN `posts` ON `posts`.`user_id` = `toto`.`id`
# WHERE
# `toto`.`firstname` = 'Ruur'"
puts User.all.to_sql
# "SELECT `users`.* FROM `users`
警告:除了此处显示的内容之外,还没有对此进行测试,如果没有对边缘情况和其他陷阱进行广泛测试,我不会建议在生产环境中使用它
更新 解决所需的实现
module ARTableAlias
def alias_table_name=(val)
@alias_table_name = val
end
def alias_table_name
@alias_table_name ||= "#{self.table_name}_alias"
end
def alias_source
@alias_klass ||= Class.new(self).tap do |k|
k.all.source.left.table_alias = alias_table_name
end
end
end
然后用法为:
class User < ApplicationRecord
extend ARTableAlias
alias_table_name = :toto
end
User.alias_source.where(first_name = 'Ruur')
#=> SELECT `toto`.* FROM `users` `toto` WHERE `toto`.`first_name` = 'Ruur'
User.where(first_name = 'Ruur')
#=> SELECT `users`.* FROM `users` WHERE `users`.`first_name` = 'Ruur'
User.alias_source === User.alias_source
#=> true
有没有办法在单个范围的上下文中为源 table 添加别名?
我试过了:
scope = User.all
scope.arel.source.left.table_alias = "toto"
scope.where(firstname: nil) => "SELECT `toto`.* FROM `users` `toto` WHERE `toto`.`firstname` IS NULL"
问题是模型 class 保留了所有后续查询的别名:
User.all => "SELECT `toto`.* FROM `users` `toto`"
编辑
我把这个方法加到ApplicationRecord
def self.alias_source(table_alias)
klass = Class.new(self)
klass.all.source.left.table_alias = table_alias
klass
end
现在,我可以做到:
User.alias_source(:toto).where(firstname: nil) => "SELECT `toto`.* FROM `users` `toto` WHERE `toto`.`firstname` IS NULL"
我把这个方法添加到ApplicationRecord
def self.alias_source(table_alias)
klass = Class.new(self)
klass.all.source.left.table_alias = table_alias
klass
end
现在,我可以做到:
User.alias_source(:toto).where(firstname: nil) => "SELECT `toto`.* FROM `users` `toto` WHERE `toto`.`firstname` IS NULL"
由于创建从 ActiveRecord::Base
继承的 Anonymous 类 会导致内存膨胀,我不确定我是否会推荐它(参见此处:https://github.com/rails/rails/issues/31395)。
也许更好的实现方式是以块形式执行,例如将别名 table 生成到块然后再将其设置回去?
例如
def self.with_table_alias(table_alias, &block)
begin
self.all.source.left.table_alias = table_alias
block.call(self)
ensure
self.all.source.left.table_alias = nil
end
end
用法
User.with_table_alias(:toto) do |scope|
puts scope.joins(:posts).where(firstname: "Ruur").to_sql
end
# "SELECT `toto`.*
# FROM
# `users` `toto`
# INNER JOIN `posts` ON `posts`.`user_id` = `toto`.`id`
# WHERE
# `toto`.`firstname` = 'Ruur'"
puts User.all.to_sql
# "SELECT `users`.* FROM `users`
警告:除了此处显示的内容之外,还没有对此进行测试,如果没有对边缘情况和其他陷阱进行广泛测试,我不会建议在生产环境中使用它
更新 解决所需的实现
module ARTableAlias
def alias_table_name=(val)
@alias_table_name = val
end
def alias_table_name
@alias_table_name ||= "#{self.table_name}_alias"
end
def alias_source
@alias_klass ||= Class.new(self).tap do |k|
k.all.source.left.table_alias = alias_table_name
end
end
end
然后用法为:
class User < ApplicationRecord
extend ARTableAlias
alias_table_name = :toto
end
User.alias_source.where(first_name = 'Ruur')
#=> SELECT `toto`.* FROM `users` `toto` WHERE `toto`.`first_name` = 'Ruur'
User.where(first_name = 'Ruur')
#=> SELECT `users`.* FROM `users` WHERE `users`.`first_name` = 'Ruur'
User.alias_source === User.alias_source
#=> true