使用具有非标准主键列的迁移添加引用列

时间:2016-05-24 12:17:44

标签: mysql ruby-on-rails ruby-on-rails-4 database-migration referential-integrity

我的模型有一个非导轨的传统主键。

class Guoid < ActiveRecord::Base
  self.primary_key = :guoid
end

和相关的迁移

class CreateGuoids < ActiveRecord::Migration
  def change
    create_table :guoids, id: false do |t|
      t.integer :guoid, limit: 8, auto_increment: true, primary_key: true
      t.integer :parent_guoid, limit: 8
      t.string :resource_type, limit: 60
    end
  end
end

现在我想在另一个模型中引用此模型,并尝试使用references创建无法工作的迁移。

class ContentUnit < ActiveRecord::Base
  self.primary_key = :guoid
end

class Content < ActiveRecord::Base
  self.primary_key = :guoid
  belongs_to :user
  belongs_to :content_unit
end

和相关的迁移

class CreateContents < ActiveRecord::Migration
  def change
    create_table :contents, id: false do |t|
      t.references :content_unit, index: true, foreign_key: true
      t.references :user, index: true, foreign_key: true
    end
  end
end

当我运行迁移时,我收到了以下错误。

Mysql2::Error: Can't create table `myapp_development_cr1`.`#sql-54a_308` (errno: 150 "Foreign key constraint is incorrectly formed"): ALTER TABLE `contents` ADD CONSTRAINT `fk_rails_823443bd0d`
FOREIGN KEY (`content_unit_id`)
  REFERENCES `content_units` (`id`)

我希望在content_unit_guoid表中引用contents表中创建guoid个外键。

我使用activerecord-mysql-awesome gem来处理非rails约定主键。

这是一个触发器,它首先在guoids表中创建一个记录,并使用它的pk作为目标表的pk。

guids

2 个答案:

答案 0 :(得分:2)

对于ActiveRecord甚至一般来说,这不是一个可行的数据库设计。

ActiveRecord(以及任何体面的ORM)要求每个表都有一个主键。这就是启用关系并让Rails区分记录的原因。

class Content < ActiveRecord::Base
  self.primary_key = :guoid 
  belongs_to :user
end

由于self.primary_key = :guoid引用contents.guoid而非guoids.guoid,因此无效。您不能将ActiveRecord中的关系用作主键。即使你可能这真的是性能杀手,因为每个查询都需要加入guoids表 - 甚至递归!

Rails是强烈的约定驱动,如果你花一点时间学习Rails的方式来对抗框架,让它像框架X一样工作,那么你真的会微笑。轨道指南是一个很好的起点。

主键为id,外键列为_id。如果你必须与其他开发人员合作,你将不那么大惊小怪,不要像白痴一样对待。

在某些有效情况下,您可能希望使用唯一标识符(UUID)而不是自动递增值。例如,如果您有多个数据库,则自动递增值可能会导致竞争条件。但在这种情况下,您仍然需要在每个表上使用主键 - 区别仅在于主键的内容。

这可以通过使用在应用程序级别上具有较低碰撞机会的算法生成哈希,或者最近通过在数据库中使用二进制UUID类型来生成。后者今天更受欢迎。

不使用关系。 AR就是这样不行。

使用非标准外键。

如果您遵循惯例,belongs_toreference宏就是一个可行的示例。

对于与您在迁移中手动创建它所需的约定不匹配的外键约束:

class CreateStores < ActiveRecord::Migration
  def change
    create_table :contents do |t|
      t.references :manger, index: true, foreign_key: false
    end
    add_foreign_key :stores, :users, column: 'manager_id', primary_key: 'uuid'
  end
end

请注意,这不会解决您的问题,因为您的一般方法不可行!

答案 1 :(得分:1)

那么,你试图使用这个正确的内容表到guoids表创建一个外键吗?

t.references :content_unit, index: true, foreign_key: true

引用将表名作为参数,并尝试在其上查找名为id的列,以在表之间生成外键。因此,您可以在错误消息中看到它试图在content_units表上找到列ID。这绝不是指你的guoids。

想要全局唯一标识符(通常是GUID或UUID)是完全合理的,但是我不知道为什么要将它们存储在一个单独的表中,然后(我假设)将创建一些外键来创建一些大量多对多的表连接数据库中的每个表?看起来真的不可扩展.Postgress很好地处理你的uuid,但正如我过去的,它看起来像你使用mysql。这就是我如何做到的。

模型

Class Teacher < ActiveRecord::Base
  has_many :students

  before_validation :generate_id
  self.primary_key = :id

private

  def generate_id
    write_attribute(:id, SecureRandom.uuid) unless read_attribute(:id)
  end
end


Class Student < ActiveRecord::Base
  belongs_to :teacher
end

迁移

create_table :teachers, id: false do |t|
  t.string :id, null: false, unique: true, limit: 36
end

create_table :students do |t|
  t.string :teacher_id, limit: 36
end

add_foreign_key :model_a, :model_b