创建 link table 以连接同一模型的一对一关系

Creating link table to connect one to one relationship of same model

我想确保 table join 与 joining table 具有很强的一对一关系。以下是例子 运行 生成器

bundle exec rails g model Page name:string content:text
bundle exec rails g model PageAssociation left_page_id:integer right_page_id:integer

在 运行 这些生成器之后,我必须按照

更改我的 page 模型
class Page < ApplicationRecord
  has_one :left_page_association, :foreign_key => :left_page_id,
           :class_name => 'PageAssociation'
  has_one :right_page_association, :foreign_key => :right_page_id,
           :class_name => 'PageAssociation'
  has_one :left_page, :through => :left_page_association,
           :source => :right_page
  has_one :right_page, :through => :right_page_association,
           :source => :left_page
end

在我的协会table模特page_association

class PageAssociation < ApplicationRecord
  belongs_to :left_page, class_name: 'Page'
  belongs_to :right_page, class_name: 'Page'
end

现在使用 rails c 我可以执行以下操作

page_one = Page.create(name: 'first page', content: 'first page content')
page_two = Page.create(name: 'second page', content: 'second page content')
PageAssociation.create(left_page: page_one, right_page: page_two)
page_one.left_page
page_two.right_page

一切正常,正在返回页面。但我也可以这样做

page_three = Page.create(name: 'third page', content: 'third page content')
PageAssociation.create(left_page: page_one, right_page: page_three)

当然,由于 has_one,它仍然显示在关系上,但它正在创建另一个关系。所以我想关注

  1. 只能创建一个关联,不能再创建一个
  2. 因为我们有数百万条记录,所以最好的方法是什么
  3. 是不是一个用page.left_page?或者是否有任何其他优化方法来做同样的事情。
  4. 我应该使用以下迁移行添加 indexing 吗?影响性能吗
add_foreign_key :page_associations, :pages, column: :left_page_id
add_foreign_key :page_associations, :pages, column: :right_page_id

我更改了迁移以使列值唯一,因此当我创建另一个具有相同 page.id 的 PageAssociate 时它现在会出错,但这是正确的方法吗?

      t.integer :left_page_id, null: false, index: { unique: true }
      t.integer :right_page_id, null: false, index: { unique: true }

我在解决什么问题

所以我有书 table 我有多本书,但有些书互相借页。所以假设我有 book-A 和 book-B,所以我想显示 book-a 与 book-b 的关系。如果它们是相关的,那么我将创建链接到 book-b 第 20 页的 book-a 第 10 页的另一个关系,因此当我单击 book-a 进行同步时,它将带来我书中 book-b 第 20 页的所有更改-一种。 以上是我连接两本书的第一步。我知道使用自连接和密钥解决它是最好的,但我不能这样做,因为我们有大量记录,所以这是不可能的。所以我用上面的方法来做到这一点。但是我确实在模型中添加了对数据库和验证的唯一约束来解决它。但我觉得这仍然不是一个好方法。 稍后我会做以下

main-book , main-book-page, secondary-book-page

这将允许我将后面的页面相互装订。书名和页数是虚构的,实际实体是不同的。

这里你想要的只是一个普通的多对多table设置:

class Book < ApplicationRecord
  has_many :book_pages
  has_many :pages, through: :book_pages
end

class Page < ApplicationRecord
  has_many :book_pages
  has_many :books, through: :book_pages
end

class BookPage < ApplicationRecord
  belongs_to :book
  belongs_to :page
  validates_uniqueness_of :book_id, scope: :page_id
end

这种情况下的唯一性可以通过添加唯一索引来保证:

add_index :book_pages, [:book_id, :page_id]

为什么?

M2M join table 有两个任意分配的外键的设置不是一个很好的设计,并且不能很好地与 ActiveRecord 一起工作,因为您无法定义与 [=14= 的关联] 条款。

每个关联只能有一个外键。这意味着您不能将其视为同质集合,也不能急切加载它。

这意味着您需要像这样编写蹩脚的联接,而不是能够使用适当的关联:

Book.joins(
  "LEFT JOIN pages_assocations pa ON pa.left_page_id = books.id OR pa.left_page_id = books.id"
)

而且创建间接关联的时候也要写蒸堆

虽然每个 book/page 组合只有一行的 table 设置似乎在开始时需要更多行,但它也更加灵活,因为您可以通过子查询映射书籍之间的关联,横向连接或分组并计算匹配数。

class Book < ApplicationRecord
  has_many :book_pages
  has_many :pages, through: :book_pages

  def books_with_pages_in_common
     Book.where(
        id: BookPage.select(:book_id)
                    .where(page_id: pages)
     )
  end
end