【问题标题】:dynamic table names for Active Record modelsActive Record 模型的动态表名
【发布时间】:2009-05-13 04:29:15
【问题描述】:

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

我不擅长清楚地解释这一点(如您所知!)。让我尝试 并用一个具体的例子来解释。假设我们有一辆汽车,它有 一个或多个轮子。通常我们会用 Car 表和 轮子表是这样的:

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,其中包含特定汽车的轮子。如果我 想为一辆车找到一套轮子我首先要找出哪个 Wheels 表通过 car_to_wheel_table_map 表使用,然后查看 在 car_to_wheel_table_map 中指定的轮表中向上记录。

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

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

编辑:请注意,将架构更改为更接近 AR 想要的不是一种选择。各种遗留代码库都依赖此架构,实际上无法修改。

【问题讨论】:

标签: ruby-on-rails activerecord


【解决方案1】:

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

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

【讨论】:

  • 谢谢,我去看看。不确定它是否完全符合我的要求,但可以给我一些关于如何自己解决这个问题的见解。
【解决方案2】:

您可以通过以下方式做到这一点。基础(在 70 行代码之前)是:

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

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

#!/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

【讨论】:

  • 感谢 Erik,但不幸的是,我们在设计时不知道表的名称。事实上,当我的 Rails 应用程序运行时,我们需要支持将新表添加到系统中。我认为任何解决方案都需要在运行时动态发现表名。
  • 我喜欢你的回答:)。 @Pete:也许您可以动态创建 CAR_TYPES 然后重新加载这部分代码?我不知道该怎么做,但我认为红宝石是可能的。顺便说一句,这个架构真的很疯狂。
  • klew.. 实际上,该模式在表大小受限(移动设备)以及后端数据必须支持所述设备的系统中非常常见。它还减少了查询时间和数据库负载。. 对具有 100 万条记录的表进行分组.. 然后对具有 1000 万条记录的表执行相同的查询。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    我会与模型中的自定义函数进行这种关联:

    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 映射您的数据,您必须定义它。或许您可以使用现有的一张带轮子的桌子制作此模型。

    我没有测试它;)。

    【讨论】:

    • 在其中硬编码 SQL 似乎有点脆弱。这也使我无法使用任何不错的 AR 关联魔法(例如 my_car.wheels.create、my_car.wheels
    • 是的.. 我的第一个想法是如何使用这种方法编辑记录。
    【解决方案5】:

    很遗憾,我知道你的烦恼。想对数据库中的表进行分区的人一定被你的老板解雇并被我雇用了;-)

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

    是使用尼克博士的魔法模型。

    干杯

    【讨论】:

      【解决方案6】:

      假设 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。 您还可以做一些事情来确保您的表存在。

      现在写信给桌子。我不确定那会如何工作。我只读过它。 也许它也很简单。

      【讨论】:

      • 可能会考虑在类范围内添加一个 make 属性。不知道这将如何与线程一起玩?
      【解决方案7】:

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

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

      【讨论】:

      • 旧版。现有的、可怕的、可怕的更改代码也在使用这些表。除此之外,能够像这样对数据库中的一些非常大的表进行分区使得一些操作任务更容易(更改索引、删除旧数据等)。
      • 你能给我一个或两个代码库当前如何访问它的例子吗?我们也许可以编写一些方法/named_scopes/something 来模拟当前代码库正在做什么。然后你可以慢慢重构,而不是通过更新破坏整个系统。
      • 在不同的时间用不同的语言编写的各种遗留代码库可能会或可能不会使用这些表。没有人真正知道,最初的开发人员早已不复存在,等等等等。通过更改架构来解决这个问题确实不可行。再说一次,保持模式发挥作用可能是有意义的,这也是有操作原因的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多