【问题标题】:Add a reference column using migration having non standard primary key column使用具有非标准主键列的迁移添加参考列
【发布时间】:2016-09-21 15:40:01
【问题描述】:

我有一个具有非 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 表中创建content_unit_guoid 外键,并在guoids 表中引用guoid

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

这是一个触发器,它首先在guids 表中创建一条记录,并将其 pk 作为目标表的 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 ;

【问题讨论】:

  • 这是一个遗留应用程序,你必须使用这种奇怪的设置,还是你只是喜欢让自己变得困难?
  • 没有。它不是遗留应用程序。我必须创建一个全局唯一的 id 并在不同的表中使用它。我的非 Rails 背景技术经理建议使用此类 ID。
  • 他是个白痴。经理们通常是:)。您需要在每个表上都有主键才能使 ActiveRecord 工作。我强烈建议您坚持使用 rails 默认设置。 id 用于每个表,_id 用于外键。
  • 首先,检查您是否真的需要全局唯一 ID。如果你这样做,那么我建议使用 uuid,而不是更改约定。改变它们会给你带来很多痛苦。
  • 附言。 GUID 真的只是微软为我们其他人所说的 UUID 代言

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


【解决方案1】:

这对于 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 一样工作,那么您真的会微笑。rails 指南是一个很好的起点。

主键使用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

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

【讨论】:

  • 谢谢,@max。我对节省时间和精力的“Rails 方式”有相当的了解。但我处于其他人是决策者的情况。
  • 那么您需要向您的经理解释该方法不可行并提出可行的解决方案 - 如果您想在 MySQL 中使用 UUID,那么您将使用每个表的 PK 并使用 MySQL 生成 UUID :s uuid 函数。虽然如果你刚开始,我真的会推荐 Postgres。
  • 是的。我可以说服使用id 而不是guoid。谢谢,@max。
【解决方案2】:

那么,您尝试使用这个正确的方法来创建从内容表到 guoids 表的外键?

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

references 将表名作为参数,并尝试在其上找到一个名为 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

【讨论】:

    猜你喜欢
    • 2011-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-28
    • 2017-04-03
    • 2014-12-27
    • 2016-09-08
    相关资源
    最近更新 更多