Rails has_and_belongs_to_many迁移

130
我有两个模型restaurantuser,我想执行一个has_and_belongs_to_many关系。
我已经进入模型文件并添加了has_and_belongs_to_many :restaurantshas_and_belongs_to_many :users 我认为此时我应该能够像Rails 3一样做一些事情:
rails generate migration ....

但是我尝试过的所有方法都似乎失败了。我相信这应该是很简单的问题,因为我刚学Rails,还在学习中。

4个回答

277

您需要添加一个单独的连接表,其中只包含restaurant_iduser_id(没有主键),并按字母顺序排列。

首先运行迁移,然后编辑生成的迁移文件。

Rails 3

rails g migration create_restaurants_users_table

Rails 4:

rails g migration create_restaurants_users

Rails 5

rails g migration CreateJoinTableRestaurantUser restaurants users

根据文档

如果表名包含 JoinTable,还有一个生成器可以产生连接表:


您的迁移文件(请注意:id => false;它可以防止创建主键):

Rails 3

class CreateRestaurantsUsers < ActiveRecord::Migration
  def self.up
    create_table :restaurants_users, :id => false do |t|
        t.references :restaurant
        t.references :user
    end
    add_index :restaurants_users, [:restaurant_id, :user_id]
    add_index :restaurants_users, :user_id
  end

  def self.down
    drop_table :restaurants_users
  end
end

Rails 4

class CreateRestaurantsUsers < ActiveRecord::Migration
  def change
    create_table :restaurants_users, id: false do |t|
      t.belongs_to :restaurant
      t.belongs_to :user
    end
  end
end

t.belongs_to会自动创建必要的索引。 def change会自动检测向前或回滚迁移,不需要使用up / down。

Rails 5

create_join_table :restaurants, :users do |t|
  t.index [:restaurant_id, :user_id]
end

注意:还有一个自定义表名的选项,可以作为参数传递给create_join_table,名为table_name。来自文档 默认情况下,联接表的名称来自于提供给create_join_table的前两个参数的字母顺序的并集。要自定义表的名称,请提供一个:table_name选项:

8
@Dex - 只是出于好奇,您能解释一下为什么要使用第二个复合索引,并定义相反的列顺序吗?我原本以为列的顺序并不重要。虽然我不是DBA,但我希望能进一步了解。谢谢! - plainjimbo
11
@Jimbo,你不需要那样做,这取决于你的查询。索引从左到右读取,因此如果你在搜索 restaurant_id,第一个将是最快的。如果你在搜索 user_id,第二个将有所帮助。如果你同时搜索两者,则我认为数据库聪明到只需要一个。所以我想第二个不用复合索引也可以。这只是一个例子。这是一个Rails问题,所以在DB部分发布将得到更完整的答案。 - Dex
3
第二个索引有些冗余 - 只要您在 SQL 查询中同时查询 restaurant_id 和 user_id,它们在 SQL 中的顺序并不重要,第一个索引将被使用。如果您只查询 restaurant_id,则也会使用第一个索引。第二个索引只需要在:user_id上,而且只有在您仅查询:user_id时才会使用它(因为第一个索引由于其键的顺序而无法帮助您)。 - Yardboy
13
在Rails 4中,迁移的命名应为rails g migration create_restaurants_users,末尾不需要加上"table"。 - Fa11enAngel
7
你也可以使用rails g migration CreateJoinTableRestaurantUser restaurant user来创建餐厅用户联接表。阅读http://guides.rubyonrails.org/migrations.html#creating-a-join-table了解更多信息。 - eKek0
显示剩余7条评论

42
这里的答案已经有些过时了。从Rails 4.0.2开始,你的迁移将使用create_join_table
要创建迁移,请运行:
rails g migration CreateJoinTableRestaurantsUsers restaurant user

这将生成以下内容:
class CreateJoinTableRestaurantsUsers < ActiveRecord::Migration
  def change
    create_join_table :restaurants, :users do |t|
      # t.index [:restaurant_id, :user_id]
      # t.index [:user_id, :restaurant_id]
    end
  end
end

如果您想对这些列进行索引,请取消相应行的注释,然后就可以开始使用了!


5
通常我会取消其中一个索引行的注释,并在其上添加 unique: true。这样可以防止创建重复的关系。 - Toby 1 Kenobi

26

在创建联接表时,请注意需要按字母顺序列出两个表的名称,以确保迁移名称/类名正确。如果您的模型名称相似,例如“abc”和“abb”,这可能会给您带来麻烦。如果运行以下命令

rails g migration create_abc_abb_table

您的关系将不会按预期工作。 您必须使用

rails g migration create_abb_abc_table

相反地。


1
先来哪个?foo 还是 foo_bar? - B Seven
1
在Rails控制台中运行:["foo_bar", "foo", "foo bar"].sort # => ["foo", "foo bar", "foo_bar"] Unix的排序结果也相同。 - Shadoath

6

对于HABTM关系,您需要创建一个连接表。只有一个连接表,该表不应具有id列。尝试使用此迁移。

注:HABTM是has_and_belongs_to_many的缩写,意为多对多关系。
def self.up
  create_table :restaurants_users, :id => false do |t|
    t.integer :restaurant_id
    t.integer :user_id
  end
end

def self.down
  drop_table :restaurants_users
end

您必须查看这个关于Rails关联的指南教程


我认为你不需要一个模型,而且我在链接中也没有看到关于HABTM关系需要模型的任何内容。 - B Seven
1
为了加快生成的查询速度,请在id字段上添加索引。 - Martin Röbert

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