【问题标题】:给源表起别名
【发布时间】:2022-01-22 18:25:21
【问题描述】:

有没有办法在单个作用域的上下文中为源表设置别名?

我试过了:

scope = User.all
scope.arel.source.left.table_alias = "toto"
scope.where(firstname: nil) => "SELECT `toto`.* FROM `users` `toto` WHERE `toto`.`firstname` IS NULL"

问题是模型类为所有后续查询保留别名:

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"

【问题讨论】:

    标签: ruby-on-rails activerecord ruby-on-rails-5 arel


    【解决方案1】:

    我将此方法添加到 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"
    

    【讨论】:

      【解决方案2】:

      由于创建从ActiveRecord::Base 继承的匿名类会导致内存膨胀,我不确定我是否会推荐它(参见此处:https://github.com/rails/rails/issues/31395)。

      也许更好的实现是以块形式执行,而不是例如将别名表生成给块,然后再将其设置回来?

      例如

      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
      

      【讨论】:

      • 这意味着匿名类永远不会被垃圾收集?当您创建它们时,内存会不断增加?或者我们是否在谈论广泛创建匿名类(例如在测试服中创建数千个)
      • @Ruur 一般来说,在 ruby​​ 中,匿名类在失去引用时会被 GC。问题是 Rails 跟踪所有后代或ActiveRecord::Base(当您调用Class.new(self) 时会发生这种情况,因为self 继承自ActiveRecord::Base)。这意味着这些类永远不会丢失引用,因此永远不会被 GC 处理,这会导致内存膨胀
      • 我现在明白了。但是,我们如何释放匿名类使用的内存呢?重启应用?
      • 我们在某些特定范围内使用此代码。因此,我们要创建的别名是事先知道的。现在我知道匿名类保存在内存中,我们可以写这样的东西来防止一遍又一遍地创建相同的匿名类吗? ruby klass_name = "#{table_alias}_table_alias".camelize begin klass = klass_name.constantize rescue NameError =&gt; e klass = Object.const_set(klass_name, Class.new(self)) klass.all.source.left.table_alias = table_alias klass end
      • @Ruur 更新了我推荐的您所需功能的实现。 Class 仍然是匿名的,但它存储在类实例变量中以供重用。
      猜你喜欢
      • 1970-01-01
      • 2012-10-07
      • 2011-10-10
      • 1970-01-01
      • 2017-05-30
      • 2017-02-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多