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

Add a reference column using migration having non standard primary key column

我有一个模型有一个非rails常规主键。

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`)

我希望在 contents table 中创建 content_unit_guoid 外键引用 guoids table 中的 guoid

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

这是一个触发器,它首先在 guids table 中创建一条记录,并使用它的 pk 作为目标 table.

的 pk
DELIMITER $$
CREATE TRIGGER `content_before_insert` BEFORE INSERT ON `content`
 FOR EACH ROW BEGIN
IF NEW.guoid = 0 THEN
    INSERT INTO `content`.guoids (resource_type)
        VALUES('Content');
    SET NEW.guoid = LAST_INSERT_ID();
END IF;
END
$$
DELIMITER ;

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

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

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

这永远行不通,因为 self.primary_key = :guoid 引用 contents.guoid 而不是 guoids.guoid。您不能将 ActiveRecord 中的关系用作主键。即使可以,这也确实会成为性能杀手,因为每个查询都需要加入 guoids table - 甚至递归!

Rails 是强烈的约定俗成的,如果你花一点时间学习 Rails 方式而不是与框架作斗争以使其像框架 X 一样工作,那么真的会对你微笑。rails 指南是一个很好的起点。

坚持使用 id 作为主键,使用 _id 作为外键列。如果您必须与其他开发人员合作,您将不会大惊小怪,也不会被当作白痴对待。

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

这可以通过使用在应用程序级别上发生冲突的可能性很小的算法生成哈希来完成,或者最近通过在数据库中使用二进制 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

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

那么,您尝试从内容 table 到 guoids table 的外键使用正确吗?

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

references 将 table 名称作为参数,并尝试在其上找到名为 id 的列以在 table 之间创建外键。因此,您可以在错误消息中看到它试图在 content_units table 上查找列 ID。这绝不是在引用您的 guoid。

想要全局唯一标识符(通常是 GUID 或 UUID,但是我不确定为什么要将它们存储在单独的 table 然后(我假设)将一切它的外键创建了一些连接数据库中每个 table 的大量多对多 table?似乎真的无法扩展。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