在Rails中创建多对多关系

时间:2011-02-25 17:46:14

标签: 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时,在两者之间使用单独的表是否可行?

4 个答案:

答案 0 :(得分:132)

您想要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

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

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

bin/rails g migration CreateCategoriesUsersJoinTable

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

对于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

Rails&lt; 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

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

User.categories  #=> [<Category @name="Sports">, ...]
Category.users   #=> [<User @name="UserA">, ...]
User.categories.empty?

答案 1 :(得分:1)

最受欢迎的是“单音和物协会”,您可以这样做:

class Book < ApplicationRecord
  has_many :book_author
  has_many :author, through: :book_author
end

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

class Author < ApplicationRecord
  has_many :book_author
  has_many :book, through: :book_author
end
  

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

答案 2 :(得分:0)

仅补充上述coreyward的回答: 如果您已经有一个具有 belongs_to has_many 关系的模型,并且想要使用同一表创建一个新的 has_and_belongs_to_many 关系,到:

  rails g migration CreateJoinTableUsersCategories用户类别
 

然后

  rake db:migrate
 

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

User.rb:

  class Region  

Category.rb

  class Facility  

为了用旧数据填充新的联接表,您需要在控制台中:

  User.all.find_each做| u |
  Category.where(user_id:u.id).find_each做| c |
    u。类别<< c
  结束
结束
 

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

答案 3 :(得分:0)

如果您想添加关于关系的附加数据has_many :things, through: :join_table 可能就是您要找的。但是,通常情况下,您不需要在连接关系中添加额外的元数据(如角色),在这种情况下,has_and_belongs_to_many 绝对是最简单的方法(如此 SO 帖子的公认答案)。

但是,假设您正在构建一个论坛站点,其中有多个论坛,并且需要支持在他们参与的每个论坛中担任不同角色的用户。允许跟踪用户与某个论坛的关系可能会很有用关于加入本身的论坛:

class Forum
  has_many :forum_users
  has_many :users, through: :forum_users

  has_many :admin_forum_users, -> { administrating }, class_name: "ForumUser"
  has_many :viewer_forum_users, -> { viewing }, class_name: "ForumUser"

  has_many :admins, through: :admin_forum_users, source: :user
  has_many :viewers, through: :viewer_forum_users, source: :user
end

class User
  has_many :forum_users
  has_many :forums, through: :forum_users
end

class ForumUser
  belongs_to :user
  belongs_to :forum

  validates :role, inclusion: { in: ['admin', 'viewer'] }

  scope :administrating, -> { where(role: "admin") }
  scope :viewing, -> { where(role: "viewer") }
end

你的迁移看起来像这样

class AddForumUsers < ActiveRecord::Migration[6.0]
  create_table :forum_users do |t|
      t.references :forum
      t.references :user

      t.string :role

      t.timestamps
  end
end