Active Record模型的动态表名称

时间:2009-05-13 04:29:16

标签: ruby-on-rails activerecord

我有一个有趣的Active Record问题,我不太清楚是什么 最干净的解决方案是。我正在集成的遗留数据库 在其架构中有一个奇怪的皱纹,其中有一个逻辑表 '分区'到几个物理表。每张桌子都一样 结构,但包含有关不同项目的数据。

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

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表使用wheel表,然后查看 up car_to_wheel_table_map中指定的wheel表中的记录。

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

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

编辑:请注意,将模式更改为更接近AR所需的模式不是一种选择。各种遗留代码库依赖于此模式,无法实际修改。

7 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:1)

这是你可以做到的一种方式。基础知识(在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

答案 2 :(得分:1)

相反怎么样? (这是要点: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

答案 3 :(得分: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映射。也许你可以从现有的一个带轮子的桌子上制作这个模型。

我没有测试它;)。

答案 4 :(得分:0)

可悲的是,我知道你的烦恼。无论谁想要对数据库中的表进行分区,都必须由你的老板解雇并由我雇用; - )

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

是使用Dr. Nic的魔法模型。

干杯

答案 5 :(得分:0)

假设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

您可以在其中添加一些保护来验证和缩小您的iMake。 您还可以采取措施确保您的表存在。

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

答案 6 :(得分:-2)

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

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