Rails迁移:向表中添加引用,但外键列名与Rails约定不同

85

我有以下两个模型:

class Store < ActiveRecord::Base
    belongs_to :person
end

class Person < ActiveRecord::Base
    has_one :store
end

问题在于:我正在尝试创建一个迁移来在 people 表中创建外键。然而,引用 Store 的外键列的名称不是 Rails 约定的 store_id,而是 foo_bar_store_id

如果我遵循 Rails 约定,我会像这样执行迁移:

class AddReferencesToPeople < ActiveRecord::Migration
  def change
    add_reference :people, :store, index: true
  end
end

然而这样并不管用,因为列名不是store_id,而是foo_bar_store_id。那么我该如何指定外键名不同,但仍保持index:true以维护快速性能呢?


我认为一个引用,就像一个外键,应该在属于另一个模型的模型中。因此,迁移应该显示为“add_reference:store,:person,index:true”。 - Graham John
7个回答

155

在 Rails 5.x 中,您可以像这样向表中添加不同名称的外键:

class AddFooBarStoreToPeople < ActiveRecord::Migration[5.0]
  def change
    add_reference :people, :foo_bar_store, foreign_key: { to_table: :stores }
  end
end

在一个 create_table 块内部

t.references :feature, foreign_key: {to_table: :product_features}

1
@inye 看起来那个 bug 已经被修复了。 - schpet
17
这是其中一种看似未记录的技巧,它让我同时爱恨 Rails。 - peelman
7
这也适用于 create_table 方向,例如:t.references :feature, foreign_key: {to_table: :product_features} - toobulkeh
爱恨交织的关系是最好的,但要掌握适量。 - ARK
当我在MySQL中执行此操作时,会出现ActiveRecord :: MismatchedForeignKey错误:表inventory_incomings上的列incoming_location_id与类型为bigint(20)addresses上的列id不匹配。 要解决此问题,请将inventory_incomings表上的incoming_location_id列的类型更改为:bigint - hook38
这个例子至少从5.0版本开始被记录在add_reference部分的底部,详情请见https://api.rubyonrails.org/v5.0.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference。 - tgf

84
在Rails 4.2中,您还可以使用自定义外键名称设置模型或迁移。在您的示例中,迁移将是:

在Rails 4.2中,您还可以使用自定义的外键名来设置模型或迁移。在您的示例中,迁移应该如下所示:

class AddReferencesToPeople < ActiveRecord::Migration
  def change
    add_column :people, :foo_bar_store_id, :integer, index: true
    add_foreign_key :people, :stores, column: :foo_bar_store_id
  end
end
这里有一篇关于这个话题的有趣博客文章。这里是Rails指南中的难以理解的部分。这篇博客文章确实帮助了我。
至于关联,像这样明确说明外键或类名(我认为您最初的关联被颠倒了,'belongs_to'应该在具有外键的类中):
class Store < ActiveRecord::Base
  has_one :person, foreign_key: :foo_bar_store_id
end

class Person < ActiveRecord::Base
  belongs_to :foo_bar_store, class_name: 'Store'
end

请注意,class_name必须是一个字符串。foreign_key项可以是字符串或符号。这实质上允许您使用语义化命名的关联访问嵌套属性,如下所示:

person = Person.first
person.foo_bar_store
# returns the instance of store equal to person's foo_bar_store_id

请查看belongs_tohas_one的文档,了解更多关于关联选项的信息。


1
add_column :people, :foo_bar_store_id, index: true 应该声明一个类型吗? - Chase
1
@alex 确实,最好有单独的up和down方法,而不仅仅是一个change方法。up可能看起来与答案中的change方法相同,而down则会删除该列。 - Sia

12

对于schpet的回答,这在Rails 5迁移指令create_table中可以这样使用:

create_table :chapter do |t|
  t.references :novel, foreign_key: {to_table: :books}
  t.timestamps
end

4
"t.references :novel,foreign_key: {to_table: :books}" 在 Rails API documentation 中有说明,但没有说明或提到外键可用的其他选项。这些选项可以在 add_foreign_key 方法的文档中找到。 - Jignesh Gohel
@JigneshGohel 谢谢您提供文档链接!我之前没有看到过这个。 - Allison

11

编辑:对于那些只看到勾号却没有继续阅读的人!

虽然本答案实现了使用非传统的外键列名,但是,它并没有向数据库添加fk限制。查看其他答案可以获得更合适的解决方案,这些方案使用add_foreign_key和/或'add_reference'。

注意:一定要查看其他答案,在有些情况下,被采纳的回答并不总是最佳的!

原始回答:

您可以在 AddReferencesToPeople迁移中手动添加字段和索引,如下:

add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id

然后像这样告诉你的模型外键:

class Person < ActiveRecord::Base
  has_one :store, foreign_key: 'foo_bar_store_id'
end

55
这不是数据库中的外键。 - baash05
@baash05,您能解释一下您的问题吗? - Matt
7
我认为他的意思是,这个问题特别是在询问如何在表格(数据库级别)中创建外键,而不仅仅是在模型级别。在较新的Rails版本(例如4.2),使用references的迁移生成器可以向适当的表格添加外键,以确保数据完整性。虽然我遇到了格式问题,但我认为相关代码应该类似于:add_foreign_key :people, :stores, column: :foo_bar_store_id - Randy Shelford
1
@RandyShelford 谢谢,但我认为这也没有解释baash05的评论。我的答案包括通过迁移在数据库中创建外键,并处理模型中的非标准外键。我的答案缺少什么?您提出的add_foreign_key建议已经被另一个答案涵盖了。 - Matt
如何在数据库中添加外键? - Thorin
5
为了澄清Randy / baash05的意思,这并不会在数据库中创建一个外键约束。也就是说,foo_bar_store_id可以设置为无效的store id,并且模型将通过所有验证。如果我说错了,请纠正我,但仅仅通过在模型中添加那行代码,Rails不会在保存时抛出ActiveRecord :: InvalidForeignKey:PG :: ForeignKeyViolation:ERROR错误。 - CHawk

7
# Migration
change_table :people do |t|
  t.references :foo_bar_store, references: :store #-> foo_bar_store_id
end

# Model
# app/models/person.rb
class Person < ActiveRecord::Base
  has_one :foo_bar_store, class_name: "Store"
end

很高兴有人也添加了模型参考; 这为我节省了一些时间! - Bigtrizzy

0

在底层,add_reference 只是将委托给 add_column 和 add_index,因此您只需要自己处理它:

add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id

Person模型如何知道foo_bar_store_id实际上是指向store_id的外键? - Neil
4
这不应该放在迁移文件中,而是应该在模型的关联定义中。正如Matt所提到的has_one :store, foreign_key: 'foo_bar_store_id' - boulder

0

具有不同列名的外键

add_reference(:products, :supplier, foreign_key: { to_table: :firms })

请参考文档Docs


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接