如何动态设置模型的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
我有一个遗留数据库 (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