Tengo una base de datos heredada (Oracle), en esa base de datos tengo varias tablas que contienen datos diferentes pero son estructuralmente iguales. ¡No puedo cambiar el esquema de base de datos de ninguna manera!

Quería un modelo DRY ActiveRecord para obtener los datos correctos de las tablas correctas. El problema era que necesitaba sobrescribir dinámicamente self.table_name para que funcionara.

Aquí está mi código:

ActiveRecord: clase base que será heredada por todas las tablas similares

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

Las clases infantiles se ven así:

Un

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

SI.

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

Lo anterior funciona, sin embargo, ya me he dado cuenta de que existen algunos errores al realizar búsquedas seleccionadas específicas.

¿Es este un buen enfoque? ¿Existe una mejor manera de pasar el self.table_name de forma dinámica?

Actualización:

Uno pensaría que esto debería funcionar, pero obtengo un error de que la tabla no existe ya que ActiveRecord intenta validar la tabla antes de crear un Objeto, y self.table_name no está configurado en el Modelo ListenLoc dinámicamente.

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
1
mahatmanich 29 ene. 2016 a las 15:52

2 respuestas

La mejor respuesta

Lo que me di cuenta es que podía usar superclass sin usar globales, que terminé usando. Esto no plantea un problema de condición de carrera como ocurre con los globales.

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
2
mahatmanich 2 feb. 2016 a las 08:16

En la clase Ruby, las variables se comparten en toda la jerarquía, por lo que su enfoque no funcionará. Idea general detrás de las variables de clase: no la use a menos que esté 100% seguro de saber lo que está haciendo. E incluso cuando lo está, es muy probable que haya un mejor enfoque.

En cuanto al problema real, lo que ha hecho con table_name no es SECAR, ya que agregó más líneas de las que guardó . Además, dificulta la lectura.

Sólo hay que poner

self.table_name =

Donde debería estar en cada modelo, sería conciso y legible.

Otra opción es usar constantes localizadas en su lugar que están vinculadas a la clase ListenLoc:

class ListenLoc
  def self.table_name
    TABLE_NAME
  end
end

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

¿Por qué esto funciona?

Mi entendimiento es el siguiente:

Al escribir ::TABLE_NAME, define la constante TABLE_NAME en el ámbito global.

Cuando su llamada se propaga a la clase ListenDoc, intenta resolver la constante ListenDoc::TABLE_NAME y no encuentra su alcance. Luego mira si la constante TABLE_NAME está definida en el alcance externo, y encuentra que ::TABLE_NAME está realmente definida, y su valor es 'LISTEN_ADDR_B'. Así, funciona.

Puede que no haya sido claro, ya que mi comprensión sobre el tema aún flota, pero definitivamente está relacionado con la forma en que Ruby busca constantes.

No es muy sencillo, ya que existen pocas advertencias (como ocurre con todas, después de todo).

1
Andrey Deineko 29 ene. 2016 a las 19:50