Active Record模型的动态表名称

我有一个有趣的Active Record问题,我不太确定最干净的解决方案是什么。 我正在集成的遗留数据库在其架构中有一个奇怪的皱纹,其中一个逻辑表已被“分区”到几个物理表中。 每个表具有相同的结构,但包含有关不同项的数据。

我不太清楚解释这个问题(正如你所知道的那样!)。 让我试着用一个具体的例子来解释。 假设我们有一辆车,它有一个或多个车轮。 通常我们用Car表和Wheels表来代表它:

CREATE TABLE cars ( `id` int(11) NOT NULL auto_increment, `name` varchar(255), ;etc ) CREATE TABLE wheels ( `id` int(11) NOT NULL auto_increment, `car_id` int(11) NOT NULL, `color` varchar(255), ;etc ) 

到现在为止还挺好。 但是,通过我遗留数据库中的“分区”策略,它看起来更像是:

 CREATE TABLE cars ( `id` int(11) NOT NULL auto_increment, `name` varchar(255), ;etc ) CREATE TABLE car_to_wheel_table_map ( `car_id` int(11) NOT NULL, `wheel_table` varchar(255) ) CREATE TABLE wheels_for_fords ( `id` int(11) NOT NULL auto_increment, `car_id` int(11) NOT NULL, `color` varchar(255) ) CREATE TABLE wheels_for_buicks ( `id` int(11) NOT NULL auto_increment, `car_id` int(11) NOT NULL, `color` varchar(255) ) CREATE TABLE wheels_for_toyotas ( `id` int(11) NOT NULL auto_increment, `car_id` int(11) NOT NULL, `color` varchar(255) ) 

所以这里我们有一组wheels_for_x表和一个car_to_wheel_table_map表,其中包含从car_id到特定wheels_for_x的映射,其中包含特定汽车的车轮。 如果我想找到汽车的车轮组,首先必须通过car_to_wheel_table_map表找出要使用的车轮表,然后在car_to_wheel_table_map中指定的车轮表中查找记录。

首先,有人可以告诉我这项技术是否有标准名称?

其次,有没有人对如何以一种干净的方式在Active Record中完成这项工作有任何指示。 我看到它的方式我可以有一个Wheel模型,其中可以为每个实例定义表名,或者我可以在运行时使用映射表中指定的正确表名动态创建Model类。

编辑 :请注意,更改架构更接近AR想要的不是一个选项。 各种遗留代码库依赖于此模式,无法实际修改。

数据库表分区是非常常见的做法。 如果有人之前没有这样做,我会感到惊讶。 ActsAsPartitionable怎么样? http://revolutiononrails.blogspot.com/2007/04/plugin-release-actsaspartitionable.html

另一种可能性:您的DBMS可以假装分区是一个大表吗? 我认为MySQL支持这一点。

这是你可以做到的一种方式。 基础知识(在70行代码之前)是:

  • 为每种车型创建一个has_many
  • 定义一个方法“轮子”,它使用关联中的表名来获得正确的轮子

如果您有任何疑问,请告诉我

 #!/usr/bin/env ruby %w|rubygems active_record irb|.each {|lib| require lib} ActiveSupport::Inflector.inflections.singular("toyota", "toyota") CAR_TYPES = %w|ford buick toyota| ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.establish_connection( :adapter => "sqlite3", :database => ":memory:" ) ActiveRecord::Schema.define do create_table :cars do |t| t.string :name end create_table :car_to_wheel_table_map, :id => false do |t| t.integer :car_id t.string :wheel_table end CAR_TYPES.each do |car_type| create_table "wheels_for_#{car_type.pluralize}" do |t| t.integer :car_id t.string :color end end end CAR_TYPES.each do |car_type| eval <<-END class #{car_type.classify}Wheel < ActiveRecord::Base set_table_name "wheels_for_#{car_type.pluralize}" belongs_to :car end END end class Car < ActiveRecord::Base has_one :car_wheel_map CAR_TYPES.each do |car_type| has_many "#{car_type}_wheels" end delegate :wheel_table, :to => :car_wheel_map def wheels send("#{wheel_table}_wheels") end end class CarWheelMap < ActiveRecord::Base set_table_name "car_to_wheel_table_map" belongs_to :car end rav4 = Car.create(:name => "Rav4") rav4.create_car_wheel_map(:wheel_table => "toyota") rav4.wheels.create(:color => "red") fiesta = Car.create(:name => "Fiesta") fiesta.create_car_wheel_map(:wheel_table => "ford") fiesta.wheels.create(:color => "green") IRB.start if __FILE__ == $0 

这样呢? (这是要点: http : //gist.github.com/111041 )

 #!/usr/bin/env ruby %w|rubygems active_record irb|.each {|lib| require lib} ActiveSupport::Inflector.inflections.singular("toyota", "toyota") ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.establish_connection( :adapter => "sqlite3", :database => ":memory:" ) ActiveRecord::Schema.define do create_table :cars do |t| t.string :name end create_table :car_to_wheel_table_map, :id => false do |t| t.integer :car_id t.string :wheel_table end create_table :wheels_for_fords do |t| t.integer :car_id t.string :color end create_table :wheels_for_toyotas do |t| t.integer :car_id t.string :color end end class Wheel < ActiveRecord::Base set_table_name nil belongs_to :car end class CarWheelMap < ActiveRecord::Base set_table_name "car_to_wheel_table_map" belongs_to :car end class Car < ActiveRecord::Base has_one :car_wheel_map delegate :wheel_table, :to => :car_wheel_map def wheels @wheels ||= begin the_klass = "#{wheel_table.classify}Wheel" eval <<-END class #{the_klass} < ActiveRecord::Base set_table_name "wheels_for_#{wheel_table.pluralize}" belongs_to :car end END self.class.send(:has_many, "#{wheel_table}_wheels") send "#{wheel_table}_wheels" end end end rav4 = Car.create(:name => "Rav4") rav4.create_car_wheel_map(:wheel_table => "toyota") fiesta = Car.create(:name => "Fiesta") fiesta.create_car_wheel_map(:wheel_table => "ford") rav4.wheels.create(:color => "red") fiesta.wheels.create(:color => "green") # IRB.start if __FILE__ == $0 

我会在模型中使用自定义函数进行此关联:

 has_one :cat_to_wheel_table_map def wheels Wheel.find_by_sql("SELECT * FROM #{cat_to_wheel_table_map.wheel_table} WHERE car_id == #{id}") end 

也许你可以使用与finder_sql的关联来做到这一点,但我不知道如何将参数传递给它。 我使用了模型Wheel,您必须定义是否希望您的数据由ActiveRecord映射。 也许你可以从你现有的带轮子的桌子上制作这个模型。

我没有测试它;)。

可悲的是,我知道你的麻烦。 谁想要在你的数据库中分区表必须被你的老板解雇并由我雇用;-)

无论如何,解决方案(通过RailsForum): http ://railsforum.com/viewtopic.php?id = 674

是使用Nic博士的Magic Models。

干杯

假设1:你知道你正在看什么类型的汽车,所以你可以判断它是福特还是闪避。

让我们把汽车制造在一个名为(万事万物)的属性中。 你应该稍后将其标准化,但是现在让我们保持简单。

 CREATE TABLE cars ( `id` int(11) NOT NULL auto_increment, `name` varchar(255), 'make' varchar(255), #ect ) class Wheel < ActiveRecord::Base def find_by_make(iMake) select("wheels_for_#{iMake}.*").from("wheels_for_#{iMake}"); end #... end 

你可以在那里添加一些保护来validation和缩小你的iMake。 您也可以采取措施确保您的表存在。

现在写到桌子上......我不确定这是怎么回事。 我只读过它。 也许它也很简单。

为什么不简单地将所有轮子放在一个表中并使用标准:has_many? 您可以在迁移中执行此操作:

  1. 创造新的轮子表
  2. 将数据从其他表迁移到新创建的表
  3. 删除旧表