使用具有非标准主键列的迁移添加参考列
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 不能那样工作。
- http://geekhmer.github.io/blog/2014/12/06/using-uuid-as-primary-key-in-ruby-on-rails-with-mysql-guide/
- http://blog.arkency.com/2014/10/how-to-start-using-uuid-in-activerecord-with-postgresql/
使用非标准外键。
belongs_to
和 reference
宏就是一个例子,如果您遵循约定,它们就可以正常工作。
对于不符合约定的外键约束,您需要在迁移中手动创建它:
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
我有一个模型有一个非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.
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 不能那样工作。
- http://geekhmer.github.io/blog/2014/12/06/using-uuid-as-primary-key-in-ruby-on-rails-with-mysql-guide/
- http://blog.arkency.com/2014/10/how-to-start-using-uuid-in-activerecord-with-postgresql/
使用非标准外键。
belongs_to
和 reference
宏就是一个例子,如果您遵循约定,它们就可以正常工作。
对于不符合约定的外键约束,您需要在迁移中手动创建它:
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