Activerecord/Datamapper - пусть один ребенок принадлежит многим родителям

Как бы вы установили ассоциацию activerecord/datamapper для следующего сценария:

Пользователь создает «книжную полку», на которой много книг (книжный объект имеет только ISBN, который используется для запроса API, и has_many объектов обзора, связанных с ним). Допустим, Джек создает «книжную полку» с книжным объектом. Затем предположим, что Джилл создает «книжную полку» с тем же объектом книги (у нее тот же идентификатор и те же отзывы). На данный момент объект book имеет следующий код:

class Book < ActiveRecord::Base
  has_many :reviews
end

Затем, когда вы просматриваете страницу книги (вы щелкаете ссылку на нее из «книжной полки», созданной Джеком), вы должны увидеть тот же объект книги, когда вы щелкали ссылку на нее из «книжной полки» Джилл. книжная полка» (например, обе «книжные полки» имеют ссылку на /books/23, потому что они имеют один и тот же объект книги).

Я не смог понять это с ассоциацией has_many, потому что это требует от меня создания новой книги каждый раз, когда пользователь добавляет книгу на свою «книжную полку». У меня проблемы с пониманием отношения has_and_belongs_to_many, это то, что следует использовать здесь? Мне не удалось найти подобные вопросы на SO, поэтому любая помощь будет очень признательна.

Я использую Rails 4 с Ruby 2.1.

Вот рисунок того, что я хотел бы сделать: Рисунок


person user2367593    schedule 20.12.2015    source источник


Ответы (1)


Да, вам нужно будет определить many-to-many relationship между книжной полкой и книгой. Есть два способа добиться этого в Rails:

Вариант 1) Используйте has_and_belongs_to_many

См. руководство

Согласно официальной документации has_and_belongs_to_many ассоциации:

Задает отношение «многие ко многим» с другим классом. Это связывает два класса через промежуточную таблицу соединений. Если таблица соединений не указана явно как опция, она угадывается с использованием лексического порядка имен классов. Таким образом, объединение между Developer и Project даст имя таблицы соединений по умолчанию «developers_projects», потому что «D» предшествует «P» в алфавитном порядке.

Итак, ваши классы должны выглядеть так:

class Bookshelf < ActiveRecord::Base
  has_and_belongs_to_many :books
end

class Book < ActiveRecord::Base
  has_and_belongs_to_many :bookshelves
  has_many :reviews
end

Добавьте генерацию таблицы соединений к своим миграциям:

class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
  def change
    create_table :books_bookshelves, id: false do |t|
      t.belongs_to :book, index: true
      t.belongs_to :bookshelf, index: true
    end
  end
end

Это создаст таблицу books_bookshelves в вашей базе данных. Таблица не будет иметь первичного ключа. К вашим моделям будет два внешних ключа Book и Bookshelf.

Итак, если вы вызовете self.books в контексте книжной полки пользователя, вы получите список книг на книжной полке. И наоборот, вызов self.bookshelves в контексте книги вернет набор книжных полок, которым принадлежит книга.

Проблема с этим подходом заключается в том, что каждый раз, когда вы добавляете новую книгу на книжную полку, в базе данных создается новая запись. Если вас это устраивает, нет более простого варианта, чем использовать has_and_belongs_to_many association. В противном случае я рекомендую вам выбрать вариант № 2.

Вариант 2) Используйте has_many :through

Другой вариант — использовать has_many, :through association (см. руководство). Для этого вам придется определить еще одну модель, но в некоторых случаях она может пригодиться (пример см. ниже).

Ваши классы должны выглядеть так:

class Bookshelf < ActiveRecord::Base
  has_many :books, through: :books_bookshelves
  has_many :books_bookshelves
end

class Book < ActiveRecord::Base
  has_many :bookshelves, through: :books_bookshelves
  has_many :books_bookshelves
  has_many :reviews
end

class BooksBookshelf < ActiveRecord::Base
  belongs_to :book
  belongs_to :bookshelf
end

Вероятно, самое лучшее в использовании has_many :through association — это то, что он позволяет вам добавлять пользовательские столбцы в таблицу соединения (например, добавить столбец count, чтобы отслеживать, сколько книг одного и того же типа есть на книжной полке).

Миграция будет выглядеть почти так же, как та, которую мы использовали в Варианте 1, за исключением того факта, что мы добавляем уникальное ограничение для внешних ключей (обратите внимание, что добавление ограничения необязательно):

class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
  def change
    create_table :books_bookshelves, id: false do |t|
      t.belongs_to :book, index: true
      t.belongs_to :bookshelf, index: true
      # add your custom columns here
    end
    add_index :books_bookshelves, [:book_id, :bookshelf_id], unique: true # to make sure you won't create duplicate records
  end
end

При таком подходе добавление нового было бы немного сложнее, поскольку вам нужно было бы убедиться, что вы не вставляете повторяющиеся записи в таблицу соединения. (Однако вы можете удалить уникальное ограничение из миграции, чтобы добиться точно такого же поведения, как при has_and_belongs_to_many.)

person Juraj Višňovský    schedule 20.12.2015