【问题标题】:Creating a many to many relationship in Rails在 Rails 中创建多对多关系
【发布时间】:2011-07-04 11:46:33
【问题描述】:

这是我想要实现的目标的简化示例,我对 Rails 比较陌生,并且正在努力理解模型之间的关系。

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

在这个例子中:

我的Categories 表包含五个类别:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |身份证 |姓名 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 1 |运动 | | 2 |新闻 | | 3 |娱乐 | | 4 |技术 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我的Users 表包含两个用户:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |身份证 |姓名 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 1 |用户A | | 2 |用户B | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

用户A可以选择体育和科技作为他的类别

用户B可以选择新闻、体育和娱乐

运动类别被删除,UserA 和 UserB 类别列表都反映了删除

我已经尝试过创建一个UserCategories 表,其中包含类别和用户的ID。这种工作,我可以查找类别名称,但我无法让级联删除工作,整个解决方案似乎是错误的。

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

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

【问题讨论】:

  • 其实使用UserCategory模型并没有什么问题,在大多数情况下甚至是可取的。你只需要使用User.has_many :categories, through: :user_categories。也许,可以找到一个更好的名字。
  • 对于其他人,这里有一个很好的关于这个主题的轨道:railscasts.com/episodes/47-two-many-to-many

标签: ruby-on-rails


【解决方案1】:

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

导轨
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?

【讨论】:

  • 一个额外的提示:为表创建一个复合索引。更多这里stackoverflow.com/a/4381282/14540
  • @kolrie 好点;为了彻底起见,我在这里添加了示例。干杯!
  • 愚蠢的问题,这是否意味着有必要创建一个新模型?
  • 我注意到的另一件事是连接表的命名约定。它必须按字母顺序排列。使用上面的示例,表名必须是 categories_users 而不是 users_categories。在这种情况下,“c”在“u”之前。
  • @ashwood 您不需要主键,因为它们之间的关系只有一个实例。
【解决方案2】:

最流行的是'单传递关联',你可以这样做:

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

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

【讨论】:

    【解决方案3】:

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

    rails g migration CreateJoinTableUsersCategories users categories
    

    那么,

    rake db:migrate
    

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

    用户.rb:

    class Region < ApplicationRecord
      has_and_belongs_to_many :categories
    end
    

    Category.rb

    class Facility < ApplicationRecord
      has_and_belongs_to_many :users
    end
    

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

    User.all.find_each do |u|
      Category.where(user_id: u.id).find_each do |c|
        u.categories <<  c
      end
    end
    

    您可以从 Category 和 User 表中保留 user_id 列和 category_id 列,或者创建迁移以将其删除。

    【讨论】:

      【解决方案4】:

      如果您想添加有关关系的其他数据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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-28
        相关资源
        最近更新 更多