Search code examples
ruby-on-railsrubyoracle-databaserails-activerecordclass-variables

How to dynamically set model's table_name?


I have a legacy db (oracle), in that db I have several tables that hold different data but are structurally the same. I am not allowed to change the DB schema in any way!

I wanted a DRY ActiveRecord Model to fetch the right data from the right tables. The problem was that I needed to dynamically overwrite self.table_name in order for it work.

Here is my code:

ActiveRecord:Base Class which will be inherited by all similar 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 Classes look as such:

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

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

The above works, however I have already noticed that there are some pitfalls when doing specific select lookups.

Is this a good approach? Is there a better way to pass the self.table_name dynamically?

Update:

One would think that this should work, but I get an error that the table does not exist since ActiveRecord tries to validate the table before creating an Object, and self.table_name is not set on the ListenLoc Model dynamically.

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

Solution

  • What I realized is, that I could just use superclass without using globals, which I ended up using. This does not pose a race condition issue as with 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