在Rails中创建多对多关系

fle*_*her 65 ruby-on-rails

这是我想要实现的一个简化示例,我对Rails相对较新,并且正在努力解决模型之间的关系问题.

我有两个模型,User模型和Category模型.用户可以与许多类别相关联.对于许多用户,特定类别可以出现在类别列表中.如果删除了特定类别,则应将其反映在用户的类别列表中.

在这个例子中:

我的Categories表包含五个类别:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| ID | Name                       |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1  | Sports                     | 
| 2  | News                       |
| 3  | Entertainment              |
| 4  | Technology                 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我的Users表包含两个用户:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| ID | Name                       |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1  | UserA                      | 
| 2  | UserB                      |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

UserA可以选择体育和科技作为他的类别

UserB可以选择新闻,体育和娱乐

删除了体育类别,UserA和UserB类别列表都反映了删除

我玩弄了一个创建一个UserCategories表,其中包含类别和用户的ID.这样的工作,我可以查找类别名称,但我无法得到级联删除工作,整个解决方案似乎错了.

使用我发现的belongs_to和has_many函数的例子似乎讨论了映射一对一的关系.例如,对博客文章的评论.

  • 你如何使用内置的Rails功能来表示这种多对多的关系?
  • 在使用Rails时,在两者之间使用单独的表是可行的解决方案吗?

cor*_*ard 140

你想要一段has_and_belongs_to_many感情.该指南很好地描述了它如何与图表和一切一起工作:

http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association

你最终会得到这样的东西:

# app/models/category.rb
class Category < ActiveRecord::Base
  has_and_belongs_to_many :users
end

# app/models/user.rb
class User < ActiveRecord::Base
  has_and_belongs_to_many :categories
end
Run Code Online (Sandbox Code Playgroud)

现在,您需要为Rails创建一个连接表以供使用.Rails不会自动为您执行此操作.这实际上是一个表,其中包含对每个类别和用户的引用,而没有主键.

从CLI生成迁移,如下所示:

bin/rails g migration CreateCategoriesUsersJoinTable
Run Code Online (Sandbox Code Playgroud)

然后将其打开并编辑以匹配:

对于Rails 4.0.2+(包括Rails 5.2):

def change
  # This is enough; you don't need to worry about order
  create_join_table :categories, :users

  # If you want to add an index for faster querying through this join:
  create_join_table :categories, :users do |t|
    t.index :category_id
    t.index :user_id
  end
end
Run Code Online (Sandbox Code Playgroud)

Rails <4.0.2:

def self.up
  # Model names in alphabetical order (e.g. a_b)
  create_table :categories_users, :id => false do |t|
    t.integer :category_id
    t.integer :user_id
  end

  add_index :categories_users, [:category_id, :user_id]
end

def self.down
  drop_table :categories_users
end
Run Code Online (Sandbox Code Playgroud)

有了这些,运行您的迁移,您可以使用您习惯的所有方便的访问者连接类别和用户:

User.categories  #=> [<Category @name="Sports">, ...]
Category.users   #=> [<User @name="UserA">, ...]
User.categories.empty?
Run Code Online (Sandbox Code Playgroud)

  • 我注意到的另一件事是连接表的命名约定.它必须按字母顺序排列.使用上面的示例,表名必须是categories_users而不是users_categories.在这种情况下,"u"之前的"c". (13认同)
  • 另外一个提示:为表创建一个复合索引.更多信息http://stackoverflow.com/a/4381282/14540 (9认同)
  • 愚蠢的问题,这是否意味着有必要创建一个新模型? (4认同)
  • @ashwood您不需要主键,因为之间只有一个关系实例。 (2认同)

Dar*_*ich 11

最受欢迎的是“单传递关联”,你可以这样做:

class Book < ApplicationRecord
  has_many :book_authors
  has_many :authors, through: :book_authors
end

# in between
class BookAuthor < ApplicationRecord
  belongs_to :book
  belongs_to :author
end

class Author < ApplicationRecord
  has_many :book_authors
  has_many :books, through: :book_authors
end
Run Code Online (Sandbox Code Playgroud)

has_many :through 关联通常用于与另一个模型建立多对多连接。这种关联表明声明模型可以通过第三个模型与另一个模型的零个或多个实例相匹配。例如,考虑患者预约看医生的医疗实践。参考:https : //guides.rubyonrails.org/association_basics.html#the-has-many-through-association


B-M*_*B-M 8

只是补充上面 coreyward 的答案:如果您已经有一个具有belongs_to,has_many关系的模型,并且您想has_and_belongs_to_many使用同一个表创建一个新关系,您将需要:

rails g migration CreateJoinTableUsersCategories users categories
Run Code Online (Sandbox Code Playgroud)

然后,

rake db:migrate
Run Code Online (Sandbox Code Playgroud)

之后,您需要定义您的关系:

用户.rb:

class Region < ApplicationRecord
  has_and_belongs_to_many :categories
end
Run Code Online (Sandbox Code Playgroud)

类别.rb

class Facility < ApplicationRecord
  has_and_belongs_to_many :users
end
Run Code Online (Sandbox Code Playgroud)

为了使用旧数据填充新的联接表,您需要在控制台中执行以下操作:

User.all.find_each do |u|
  Category.where(user_id: u.id).find_each do |c|
    u.categories <<  c
  end
end
Run Code Online (Sandbox Code Playgroud)

您可以保留类别和用户表中的user_id列和category_id列,也可以创建迁移以将其删除。