如何动态设置模型的table_name?

时间:2016-01-29 12:52:34

标签: ruby-on-rails ruby oracle rails-activerecord class-variables

我有一个遗留数据库(oracle),在该数据库中,我有几个表,它们包含不同的数据但结构相同。我不允许以任何方式更改数据库架构!

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

这是我的代码:

ActiveRecord:基类,将由所有类似的表继承

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

子类看起来像这样:

A

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

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

上述工作,但我已经注意到在进行特定的选择查找时存在一些缺陷。

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

更新

有人会认为这应该有效,但是我得到一个错误,即表不存在,因为ActiveRecord在创建Object之前尝试验证表,并且没有动态地在ListenLoc模型上设置self.table_name。

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

2 个答案:

答案 0 :(得分:2)

我意识到,我可以在不使用全局变量的情况下使用superclass,我最终使用了它。与globals一样,这不会造成竞争条件问题。

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

答案 1 :(得分:1)

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

至于实际问题 - 您使用table_name所做的事情并未干涸,因为您添加了比您保存的更多行。而且,它使阅读更加困难。

刚刚放

self.table_name =

它应该存在于每个模型中 - 它简洁易读。

另一种选择是使用绑定到ListenLoc类的本地化常量:

class ListenLoc
  def self.table_name
    TABLE_NAME
  end
end

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

为什么会这样?

我的理解如下:

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

当您的呼叫传播到ListenDoc类时,它会尝试解析常量ListenDoc::TABLE_NAME,但它找不到它的范围。然后查看是否在外部范围中定义了常量TABLE_NAME,并且它发现::TABLE_NAME确实已定义,并且它的值为'LISTEN_ADDR_B'。因此,有效。

我可能一直不清楚,因为我对该主题的理解仍然存在,但它肯定与Ruby搜索常量的方式有关。

这不是很直接,因为很少有警告(毕竟和所有人一样)。