如何动态设置模型的table_name?

How to dynamically set model's table_name?

我有一个遗留数据库 (oracle),在那个数据库中我有几个 tables 保存不同的数据但在结构上是相同的。我不允许以任何方式更改数据库架构!

我想要一个 DRY ActiveRecord 模型从正确的 table 中获取正确的数据。问题是我需要动态覆盖 self.table_name 才能正常工作。

这是我的代码:

ActiveRecord:Base Class 将被所有类似的 tables

继承
class ListenLoc < ActiveRecord::Base
  @@table_name = nil

  def self.table_name
    @@table_name
  end    

  default_scope { where(validated: 1).where("URL_OK >= 0") }
  scope :random_order, -> { order('DBMS_RANDOM.VALUE') }
  scope :unvalidated, -> { unscope(:where).where(validated: 0) }


  def self.get_category(cat_id)
    where("cat_id = ?", cat_id)
  end    

  def self.rand_sample(cat_id, lim)
    where("cat_id = ?", cat_id).random_order.limit(lim)
  end    
end

Child Class看起来是这样的:

一个

class ListenLocA < ListenLoc
  @@table_name = 'LISTEN_ADDR_A'
  self.sequence_name = :autogenerated
  belongs_to :category, class_name: 'ListenLocCatA', foreign_key: 'cat_id'
  belongs_to :country, class_name: 'Country', foreign_key: 'country_id'
end

B.

class ListenLocB < ListenLoc
  @@table_name = 'LISTEN_ADDR_B'
  self.sequence_name = :autogenerated
  belongs_to :category, class_name: 'ListenLocCatB', foreign_key: 'cat_id'
  belongs_to :country, class_name: 'Country', foreign_key: 'country_id'
end

以上方法有效,但我已经注意到在进行特定 select 查找时存在一些缺陷。

这是一个好方法吗?有没有更好的方法来动态传递 self.table_name

更新:

有人会认为这应该可行,但我得到一个错误,指出 table 不存在,因为 ActiveRecord 在创建 Object 之前尝试验证 table,并且 self.table_name 未在 ListenLoc 模型上动态设置。

class ListenLocB < ListenLoc
  self.table_name = 'LISTEN_ADDR_B'
  self.sequence_name = :autogenerated
  belongs_to :category, class_name: 'ListenLocCatB', foreign_key: 'cat_id'
  belongs_to :country, class_name: 'Country', foreign_key: 'country_id'
end

在 Ruby class 中,变量在整个层次结构中共享,因此您的方法将行不通。 class 变量背后的一般思想——除非你 100% 确定你知道自己在做什么,否则不要使用它。即使你是 - 也很可能有更好的方法。

至于实际问题 - 您对 table_name 所做的并没有变干,因为您添加的行数比保存的行数多。而且,它使阅读变得更加困难。

就放

self.table_name =

它应该在每个模型中的位置 - 它应该简洁易读。

另一种选择是使用绑定到 ListenLoc class:

的本地化常量
class ListenLoc
  def self.table_name
    TABLE_NAME
  end
end

class ListenLocB < ListenLoc
  ::TABLE_NAME = 'LISTEN_ADDR_B'
end

为什么这有效?

我的理解如下:

通过编写 ::TABLE_NAME,您可以在全局范围内定义常量 TABLE_NAME

当您的调用传播到 ListenDoc class 时,它会尝试解析常量 ListenDoc::TABLE_NAME,但找不到它的作用域。然后查看外层作用域中是否定义了常量TABLE_NAME,发现确实定义了::TABLE_NAME,其值为'LISTEN_ADDR_B'。因此,有效。

我可能不清楚,因为我对这个话题的理解还很模糊,但这肯定与Ruby搜索常量的方式有关。

它不是很直截了当,因为存在的警告很少(毕竟,所有的警告都一样)。

我意识到,我可以只使用 superclass 而无需使用我最终使用的全局变量。这不会像全局变量那样造成竞争条件问题。

class ListenLocB < ListenLoc
  superclass.table_name = 'LISTEN_ADDR_B' # or ListenLoc.table_name
  self.sequence_name = :autogenerated
  belongs_to :category, class_name: 'ListenLocCatB', foreign_key: 'cat_id'
  belongs_to :country, class_name: 'Country', foreign_key: 'country_id'
end